yog/operation

Graph operations - Set-theoretic operations, composition, and structural comparison.

This module implements binary operations that treat graphs as sets of nodes and edges, following NetworkX’s “Graph as a Set” philosophy. These operations allow you to combine, compare, and analyze structural differences between graphs.

Set-Theoretic Operations

FunctionDescriptionUse Case
union/2All nodes and edges from both graphsCombine graph data
intersection/2Only nodes and edges common to bothFind common structure
difference/2Nodes/edges in first but not secondFind unique structure
symmetric_difference/2Edges in exactly one graphFind differing structure

Composition & Joins

FunctionDescriptionUse Case
disjoint_union/2Combine with automatic ID re-indexingSafe graph combination
cartesian_product/2Multiply graphs (grids, hypercubes)Generate complex structures
compose/2Merge overlapping graphs with combined edgesLayered systems
power/2k-th power (connect nodes within distance k)Reachability analysis

Structural Comparison

FunctionDescriptionUse Case
is_subgraph/2Check if first is subset of secondValidation, pattern matching
is_isomorphic/2Check if graphs are structurally identicalGraph comparison

Example: Combining Graphs Safely

import yog
import yog/operation

// Two triangle graphs with overlapping IDs
let triangle1 = yog.from_simple_edges(Directed, [#(0, 1), #(1, 2), #(2, 0)])
let triangle2 = yog.from_simple_edges(Directed, [#(0, 1), #(1, 2), #(2, 0)])

// disjoint_union re-indexes the second graph automatically
let combined = operation.disjoint_union(triangle1, triangle2)
// Result: 6 nodes (0-5), two separate triangles

Example: Finding Common Structure

let common = operation.intersection(graph_a, graph_b)
// Returns only nodes and edges present in both graphs

Values

pub fn cartesian_product(
  first: model.Graph(n, e),
  second: model.Graph(m, f),
  with_first default_first: f,
  with_second default_second: e,
) -> model.Graph(#(n, m), #(e, f))

Returns the Cartesian product of two graphs.

Every node in the result is a pair #(u, v) where u is from the first graph and v is from the second graph. Node IDs in the result are integers calculated as u_index * order(second) + v_index.

Example

// Product of two paths of length 1 (single edge) is a 2x2 grid
let path = yog.from_simple_edges(Undirected, [#(0, 1)])
let grid = operation.cartesian_product(path, path, 0, 0)
// order(grid) == 4, edge_count(grid) == 4
pub fn compose(
  first: model.Graph(n, e),
  second: model.Graph(n, e),
) -> model.Graph(n, e)

Composes two graphs by merging overlapping nodes and combining their edges.

This is a simple merge where nodes with the same ID are identified.

Example

let composed = operation.compose(g1, g2)
pub fn difference(
  first: model.Graph(n, e),
  second: model.Graph(n, e),
) -> model.Graph(n, e)

Returns a graph containing nodes and edges that exist in the first graph but not in the second.

An edge is removed if it exists in both graphs. A node is removed if it exists in both graphs AND it has no remaining unique edges incident to it. If a node exists in the first graph but not the second, it is always kept.

Example

let g1 = yog.from_simple_edges(Directed, [#(1, 2), #(2, 3)])
let g2 = yog.from_simple_edges(Directed, [#(1, 2)])
let result = operation.difference(g1, g2)
// Result retains node 2, 3 and edge 2->3. Node 1 is removed.
pub fn disjoint_union(
  base: model.Graph(n, e),
  other: model.Graph(n, e),
) -> model.Graph(n, e)

Combines two graphs assuming they are separate entities with automatic re-indexing.

The second graph’s node IDs are shifted by order(base) to ensure they do not collide with IDs in the first graph.

Example

let g1 = yog.from_simple_edges(Directed, [#(0, 1)])
let g2 = yog.from_simple_edges(Directed, [#(0, 1)])
let result = operation.disjoint_union(g1, g2)
// Result has nodes 0, 1, 2, 3 and edges 0->1, 2->3
pub fn intersection(
  first: model.Graph(n, e),
  second: model.Graph(n, e),
) -> model.Graph(n, e)

Returns a graph containing only nodes and edges that exist in both input graphs.

For an edge to be present in the intersection, it must exist between the same nodes in both graphs.

Example

let g1 = yog.from_simple_edges(Directed, [#(1, 2), #(2, 3)])
let g2 = yog.from_simple_edges(Directed, [#(2, 3), #(3, 4)])
let result = operation.intersection(g1, g2)
// Result contains node 2, 3 and edge 2->3
pub fn is_isomorphic(
  first: model.Graph(n, e),
  second: model.Graph(n, e),
) -> Bool

Checks if two graphs are isomorphic (structurally identical).

Two graphs are isomorphic if there exists a bijection between their vertices that preserves adjacency. This function uses a backtracking algorithm with degree-sequence pruning.

Note: Graph isomorphism is a computationally hard problem. This implementation is suitable for small to medium-sized graphs.

Example

// Triangle (0-1-2) is isomorphic to (10-20-30)
let g1 = yog.from_simple_edges(Undirected, [#(0, 1), #(1, 2), #(2, 0)])
let g2 = yog.from_simple_edges(Undirected, [#(10, 20), #(20, 30), #(30, 10)])
operation.is_isomorphic(g1, g2) // True
pub fn is_subgraph(
  potential: model.Graph(n, e),
  container: model.Graph(n, e),
) -> Bool

Checks if the first graph is a subgraph of the second graph.

Returns true if every node in the first graph exists in the second, and every edge in the first graph also exists in the second.

Example

let g1 = yog.from_simple_edges(Directed, [#(1, 2)])
let g2 = yog.from_simple_edges(Directed, [#(1, 2), #(2, 3)])
operation.is_subgraph(g1, g2) // True
pub fn power(
  graph: model.Graph(n, e),
  k: Int,
  default_weight: e,
) -> model.Graph(n, e)

Returns the k-th power of a graph.

The k-th power of a graph G, denoted G^k, is a graph where two nodes are adjacent if and only if their distance in G is at most k.

New edges are created with the provided default_weight.

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

pub fn symmetric_difference(
  first: model.Graph(n, e),
  second: model.Graph(n, e),
) -> model.Graph(n, e)

Returns a graph containing edges that exist in exactly one of the input graphs.

Also includes all nodes that are incident to these unique edges, or nodes that exist in only one of the graphs.

Example

let g1 = yog.from_simple_edges(Directed, [#(1, 2)])
let g2 = yog.from_simple_edges(Directed, [#(2, 3)])
let result = operation.symmetric_difference(g1, g2)
// Result contains edges 1->2 and 2->3
pub fn union(
  base: model.Graph(n, e),
  other: model.Graph(n, e),
) -> model.Graph(n, e)

Returns a graph containing all nodes and edges from both input graphs.

If a node or edge exists in both graphs, the one from other takes precedence for data/weights.

Example

let g1 = yog.from_simple_edges(Directed, [#(1, 2)])
let g2 = yog.from_simple_edges(Directed, [#(2, 3)])
let result = operation.union(g1, g2)
// order(result) == 3, edge_count(result) == 2
Search Document