Graph Basics

This tutorial covers the fundamentals of creating and manipulating graphs in egraph.

Creating Graphs

egraph provides two main graph types: Graph (undirected) and DiGraph (directed).

Creating an Empty Graph

import egraph as eg

# Create an undirected graph
graph = eg.Graph()

# Create a directed graph
digraph = eg.DiGraph()

Adding Nodes

Nodes can be added with associated data:

import egraph as eg

graph = eg.Graph()

# Add nodes with data
node0 = graph.add_node("Alice")
node1 = graph.add_node("Bob")
node2 = graph.add_node("Charlie")

print(f"Added {graph.node_count()} nodes")
Added 3 nodes

The add_node() method returns a node index that you use to reference the node later.

Adding Edges

Edges connect nodes and can also carry data:

import egraph as eg

graph = eg.Graph()

# Add nodes
alice = graph.add_node("Alice")
bob = graph.add_node("Bob")
charlie = graph.add_node("Charlie")

# Add edges with data (e.g., relationship type)
graph.add_edge(alice, bob, "friend")
graph.add_edge(bob, charlie, "colleague")
graph.add_edge(alice, charlie, "friend")

print(f"Graph has {graph.node_count()} nodes and {graph.edge_count()} edges")
Graph has 3 nodes and 3 edges

Accessing Graph Data

Retrieving Node Data

import egraph as eg

graph = eg.Graph()
alice = graph.add_node("Alice")
bob = graph.add_node("Bob")

# Get node data
print(f"Node {alice}: {graph.node_weight(alice)}")
print(f"Node {bob}: {graph.node_weight(bob)}")
Node 0: Alice
Node 1: Bob

Iterating Over Nodes

import egraph as eg

graph = eg.Graph()
graph.add_node("Alice")
graph.add_node("Bob")
graph.add_node("Charlie")

# Iterate over all nodes
for node_idx in graph.node_indices():
    data = graph.node_weight(node_idx)
    print(f"Node {node_idx}: {data}")
Node 0: Alice
Node 1: Bob
Node 2: Charlie

Iterating Over Edges

import egraph as eg

graph = eg.Graph()
alice = graph.add_node("Alice")
bob = graph.add_node("Bob")
charlie = graph.add_node("Charlie")

graph.add_edge(alice, bob, "friend")
graph.add_edge(bob, charlie, "colleague")

# Iterate over all edges
for edge_idx in graph.edge_indices():
    source, target = graph.edge_endpoints(edge_idx)
    data = graph.edge_weight(edge_idx)
    print(f"Edge {source} -> {target}: {data}")
Edge 0 -> 1: friend
Edge 1 -> 2: colleague

Working with NetworkX

egraph integrates seamlessly with NetworkX for graph creation and analysis.

Converting from NetworkX

import networkx as nx
import egraph as eg

# Create a NetworkX graph
nx_graph = nx.karate_club_graph()

# Convert to egraph
graph = eg.Graph()
node_map = {}

for node in nx_graph.nodes:
    node_map[node] = graph.add_node(node)

for u, v in nx_graph.edges:
    graph.add_edge(node_map[u], node_map[v], (u, v))

print(f"Converted graph: {graph.node_count()} nodes, {graph.edge_count()} edges")
Converted graph: 34 nodes, 78 edges

Using NetworkX Algorithms

You can use NetworkX for graph analysis and egraph for layout:

import networkx as nx
import egraph as eg

# Create and analyze with NetworkX
nx_graph = nx.karate_club_graph()
communities = nx.community.greedy_modularity_communities(nx_graph)

# Convert to egraph for layout
graph = eg.Graph()
node_map = {}
for node in nx_graph.nodes:
    node_map[node] = graph.add_node(node)
for u, v in nx_graph.edges:
    graph.add_edge(node_map[u], node_map[v], (u, v))

# Apply layout
drawing = eg.DrawingEuclidean2d.initial_placement(graph)
sm = eg.StressMajorization(graph, drawing, lambda _: 100)
sm.run(drawing)

print(f"Found {len(communities)} communities")
print(f"Layout computed for {graph.node_count()} nodes")
Found 3 communities
Layout computed for 34 nodes

Directed Graphs

DiGraph works similarly to Graph but maintains edge direction:

import egraph as eg

digraph = eg.DiGraph()

# Add nodes
a = digraph.add_node("A")
b = digraph.add_node("B")
c = digraph.add_node("C")

# Add directed edges
digraph.add_edge(a, b, "depends_on")
digraph.add_edge(b, c, "depends_on")
digraph.add_edge(c, a, "depends_on")  # Creates a cycle

print(f"DiGraph: {digraph.node_count()} nodes, {digraph.edge_count()} edges")
DiGraph: 3 nodes, 3 edges

Best Practices

  1. Use meaningful node data: Store relevant information in nodes for later reference

  2. Keep track of node indices: Store the mapping between your data and node indices

  3. Choose the right graph type: Use DiGraph only when direction matters

  4. Leverage NetworkX: Use NetworkX for graph algorithms and egraph for layout

Next Steps