Yog.Transform (YogEx v1.0.0)

Copy Markdown View Source

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

TransformationFunctionComplexityUse Case
Transposetranspose/1$\mathcal{O}(1)$Reverse edge directions
Add Self-Loopsadd_self_loops/2$\mathcal{O}(V)$Add reflexivity to nodes
Remove Self-Loopsremove_self_loops/1$\mathcal{O}(E)$Remove reflexive edges
To Directedto_directed/1$\mathcal{O}(1)$Convert undirected to directed
To Undirectedto_undirected/2$\mathcal{O}(E)$Mirror directed edges symmetrically
Map Nodesmap_nodes/2$\mathcal{O}(V)$Transform node data
Map Nodes Indexedmap_nodes_indexed/2$\mathcal{O}(V)$Transform node data with node ID
Map Nodes Asyncmap_nodes_async/3$\mathcal{O}(V / \text{cores})$Parallel node transforms
Relabel Nodesrelabel_nodes/2$\mathcal{O}(V + E)$Rename node IDs
Normalize Node IDsnormalize_node_ids/1$\mathcal{O}(V \log V + E)$Reindex node IDs to 0..n-1
Update Nodeupdate_node/4$\mathcal{O}(1)$Update specific node payload
Filter Nodesfilter_nodes/2$\mathcal{O}(V + E)$Subgraph node predicate filtering
Filter Nodes Indexedfilter_nodes_indexed/2$\mathcal{O}(V + E)$Subgraph node/ID predicate filtering
Map Edgesmap_edges/2$\mathcal{O}(E)$Transform edge weights
Map Edges Indexedmap_edges_indexed/2$\mathcal{O}(E)$Transform edge weights with endpoints
Map Edges Asyncmap_edges_async/3$\mathcal{O}(E / \text{cores})$Parallel edge weight transforms
Update Edgeupdate_edge/5$\mathcal{O}(1)$Update specific edge weight
Filter Edgesfilter_edges/2$\mathcal{O}(E)$Remove edges by predicate
Mergemerge/2$\mathcal{O}(V + E)$Union two graphs
Complementcomplement/2$\mathcal{O}(V^2 + E)$Inverse graph edges
Subgraphsubgraph/2$\mathcal{O}(V + E)$Extract subset of nodes
Ego Graphego_graph/4$\mathcal{O}(V + E)$Extract k-hop neighborhood
Contractcontract/4$\mathcal{O}(\text{deg}(a) + \text{deg}(b))$Merge node pair
Quotient Graphquotient_graph/4$\mathcal{O}(V + E)$Condense partition blocks
Transitive Closuretransitive_closure/1$\mathcal{O}(V \cdot (V + E))$Compute all reachable pairs
Transitive Reductiontransitive_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

add_self_loops(graph, default_weight \\ 1)

@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)

complement(graph, default_weight)

@spec complement(Yog.Graph.t(), term()) :: Yog.Graph.t()

Returns the complement of a graph.

Time Complexity: O(V² + E)

contract(graph, a, b, combine_weight)

@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))

ego_graph(graph, node, radius \\ 1, opts \\ [])

@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)

filter_edges(graph, predicate)

@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)

filter_nodes(graph, predicate)

@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)

filter_nodes_indexed(graph, predicate)

@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)

map_edges(graph, fun)

@spec map_edges(Yog.Graph.t(), (term() -> term())) :: Yog.Graph.t()

Transforms edge weights using a function, preserving graph structure.

Time Complexity: O(E)

map_edges_async(graph, fun, opts \\ [])

@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)

map_edges_indexed(graph, fun)

@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)

map_nodes(graph, fun)

@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

map_nodes_async(graph, fun, opts \\ [])

@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)

map_nodes_indexed(graph, fun)

@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

merge(graph1, graph2)

@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)

normalize_node_ids(graph)

@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)

quotient_graph(graph, partition, combine_weight \\ &Kernel.+/2, combine_data \\ fn exist, _new -> exist end)

@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)

relabel_nodes(graph, fun \\ &:erlang.phash2/1)

@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

remove_self_loops(graph)

@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)

subgraph(graph, ids)

@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)

to_directed(graph)

@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)

to_undirected(graph, resolve)

@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

transitive_closure(graph)

@spec transitive_closure(Yog.Graph.t()) :: Yog.Graph.t()

Computes the transitive closure of the graph.

Time Complexity: O(V × (V + E))

transitive_reduction(graph)

@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))

transpose(graph)

@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}]

update_edge(graph, u, v, default, fun)

@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)

update_node(graph, id, default, fun)

@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)