# Guide: Porting Algorithms from Source to NetworKit

This guide shows how to port graph algorithms from RustworkxCore and Graphrs directly to our NetworKit implementation, avoiding graph conversions.

## Benefits of Direct Porting

1. **Zero conversion overhead** - No need to convert between graph formats
2. **Better performance** - Algorithms run directly on NetworKit's cache-friendly structure
3. **Less memory** - No temporary graph copies
4. **Type safety** - Direct access to NetworKit's NodeId and edge iteration

## Example 1: Porting PageRank from RustworkxCore

### Original RustworkxCore Code

From: https://github.com/Qiskit/rustworkx/blob/main/rustworkx-core/src/centrality/pagerank.rs

```rust
// RustworkxCore version (works with petgraph)
pub fn pagerank<G>(
    graph: G,
    alpha: f64,
    tolerance: f64,
    max_iter: usize,
) -> Result<HashMap<G::NodeId, f64>, PageRankError>
where
    G: petgraph::visit::IntoNodeIdentifiers
        + petgraph::visit::NodeCount
        + petgraph::visit::IntoNeighbors,
{
    let node_count = graph.node_count();
    let mut pagerank: HashMap<G::NodeId, f64> = HashMap::new();

    // Initialize all nodes with 1/N
    for node in graph.node_identifiers() {
        pagerank.insert(node, 1.0 / node_count as f64);
    }

    for _ in 0..max_iter {
        let mut new_pagerank = pagerank.clone();

        for node in graph.node_identifiers() {
            let mut rank = 0.0;

            // Sum contributions from in-neighbors
            for neighbor in graph.neighbors(node) {
                let neighbor_rank = pagerank[&neighbor];
                let out_degree = graph.neighbors(neighbor).count();
                rank += neighbor_rank / out_degree as f64;
            }

            new_pagerank.insert(node, (1.0 - alpha) + alpha * rank);
        }

        pagerank = new_pagerank;
    }

    Ok(pagerank)
}
```

### Ported to NetworKit (Direct Implementation)

```rust
// NetworKit version (zero conversion!)
use crate::graph::backends::networkit_rust::graph::{Graph, NodeId};
use std::collections::HashMap;

pub fn pagerank_networkit(
    graph: &Graph,
    alpha: f64,
    tolerance: f64,
    max_iter: usize,
) -> HashMap<NodeId, f64> {
    let node_count = graph.node_count();
    let mut pagerank: HashMap<NodeId, f64> = HashMap::new();

    // Initialize all nodes with 1/N
    for node in graph.nodes() {
        pagerank.insert(node, 1.0 / node_count as f64);
    }

    for _ in 0..max_iter {
        let mut new_pagerank = pagerank.clone();

        for node in graph.nodes() {
            let mut rank = 0.0;

            // Sum contributions from in-neighbors
            // (use in_neighbors for directed graphs, out_neighbors for undirected)
            for neighbor in graph.in_neighbors(node) {
                let neighbor_rank = pagerank[&neighbor.target];
                let out_degree = graph.out_neighbors(neighbor.target).len();
                rank += neighbor_rank / out_degree as f64;
            }

            new_pagerank.insert(node, (1.0 - alpha) + alpha * rank);
        }

        pagerank = new_pagerank;
    }

    pagerank
}
```

**Changes needed**:
1. `G::NodeId` → `NodeId` (our type)
2. `graph.node_identifiers()` → `graph.nodes()`
3. `graph.neighbors()` → `graph.in_neighbors()` or `graph.out_neighbors()`
4. Access neighbor as `neighbor.target` instead of just `neighbor`
5. `neighbors().count()` → `out_neighbors().len()` (cheaper!)

## Example 2: Porting Louvain from Graphrs

### Original Graphrs Code (Simplified)

```rust
// Graphrs version
pub fn louvain_partitions<T, A>(
    graph: &Graph<T, A>,
    use_modularity: bool,
) -> Vec<HashSet<Node<T, A>>>
where
    T: Clone + Eq + Hash,
    A: Clone,
{
    let mut communities: HashMap<T, usize> = HashMap::new();

    // Phase 1: Modularity optimization
    for node in &graph.nodes {
        let mut best_community = 0;
        let mut best_modularity = 0.0;

        for neighbor in graph.get_neighbors(&node.name) {
            let community = communities[&neighbor.name];
            let modularity = calculate_modularity_delta(graph, &node, community);

            if modularity > best_modularity {
                best_community = community;
                best_modularity = modularity;
            }
        }

        communities.insert(node.name.clone(), best_community);
    }

    // Convert to result format
    // ...
}
```

### Ported to NetworKit

```rust
// NetworKit version
use crate::graph::backends::networkit_rust::graph::{Graph, NodeId};
use std::collections::{HashMap, HashSet};

pub struct Community {
    pub nodes: Vec<NodeId>,
    pub modularity: f64,
}

pub fn louvain_networkit(graph: &Graph) -> Vec<Community> {
    let mut communities: HashMap<NodeId, usize> = HashMap::new();

    // Phase 1: Modularity optimization
    for node in graph.nodes() {
        let mut best_community = 0;
        let mut best_modularity = 0.0;

        // Check each neighbor's community
        for neighbor in graph.out_neighbors(node) {
            if let Some(&community) = communities.get(&neighbor.target) {
                let modularity = calculate_modularity_delta_networkit(
                    graph,
                    node,
                    community,
                    &communities
                );

                if modularity > best_modularity {
                    best_community = community;
                    best_modularity = modularity;
                }
            }
        }

        communities.insert(node, best_community);
    }

    // Convert HashMap to Vec of Communities
    let mut community_map: HashMap<usize, Vec<NodeId>> = HashMap::new();
    for (node, community) in communities.iter() {
        community_map.entry(*community).or_insert_with(Vec::new).push(*node);
    }

    community_map.into_iter()
        .map(|(_, nodes)| Community {
            modularity: 0.0, // Calculate per community
            nodes,
        })
        .collect()
}

fn calculate_modularity_delta_networkit(
    graph: &Graph,
    node: NodeId,
    community: usize,
    communities: &HashMap<NodeId, usize>,
) -> f64 {
    // Ported modularity calculation using NetworKit's neighbor iteration
    let m = graph.edge_count() as f64;
    let k_i = graph.degree(node) as f64;

    let mut delta = 0.0;
    for neighbor in graph.out_neighbors(node) {
        if communities.get(&neighbor.target) == Some(&community) {
            delta += 1.0;
        }
    }

    delta / m
}
```

**Key changes**:
1. `Graph<T, A>` → `Graph` (our concrete type)
2. `graph.nodes` → `graph.nodes()` (iterator)
3. `graph.get_neighbors()` → `graph.out_neighbors()`
4. Direct access to NetworKit's efficient degree calculation

## Example 3: Porting A* from RustworkxCore

### Original RustworkxCore A*

```rust
// RustworkxCore version
pub fn astar<G, F, H>(
    graph: G,
    start: G::NodeId,
    is_goal: F,
    edge_cost: H,
    estimate_cost: E,
) -> Option<(f64, Vec<G::NodeId>)>
where
    G: IntoEdges,
    F: Fn(G::NodeId) -> bool,
    H: Fn(G::EdgeRef) -> f64,
    E: Fn(G::NodeId) -> f64,
{
    // ... A* implementation using petgraph traits
}
```

### Ported to NetworKit

```rust
// NetworKit version - works directly with our graph!
use crate::graph::backends::networkit_rust::graph::{Graph, NodeId};
use std::collections::{BinaryHeap, HashMap};
use std::cmp::Ordering;

#[derive(Copy, Clone)]
struct State {
    cost: f64,
    position: NodeId,
}

impl Ord for State {
    fn cmp(&self, other: &Self) -> Ordering {
        other.cost.partial_cmp(&self.cost).unwrap()
    }
}

impl PartialOrd for State {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl PartialEq for State {
    fn eq(&self, other: &Self) -> bool {
        self.cost == other.cost
    }
}

impl Eq for State {}

pub fn astar_networkit<F>(
    graph: &Graph,
    start: NodeId,
    goal: NodeId,
    heuristic: F,
) -> Option<Vec<NodeId>>
where
    F: Fn(NodeId) -> f64,
{
    let mut frontier = BinaryHeap::new();
    let mut came_from: HashMap<NodeId, NodeId> = HashMap::new();
    let mut cost_so_far: HashMap<NodeId, f64> = HashMap::new();

    frontier.push(State { cost: 0.0, position: start });
    cost_so_far.insert(start, 0.0);

    while let Some(State { cost, position }) = frontier.pop() {
        if position == goal {
            // Reconstruct path
            let mut path = vec![goal];
            let mut current = goal;
            while let Some(&prev) = came_from.get(&current) {
                path.push(prev);
                current = prev;
            }
            path.reverse();
            return Some(path);
        }

        // Explore neighbors using NetworKit's efficient iteration
        for neighbor in graph.out_neighbors(position) {
            let new_cost = cost_so_far[&position] + neighbor.weight.unwrap_or(1.0);

            if !cost_so_far.contains_key(&neighbor.target)
                || new_cost < cost_so_far[&neighbor.target]
            {
                cost_so_far.insert(neighbor.target, new_cost);
                let priority = new_cost + heuristic(neighbor.target);
                frontier.push(State {
                    cost: priority,
                    position: neighbor.target,
                });
                came_from.insert(neighbor.target, position);
            }
        }
    }

    None
}
```

**Benefits**:
- Direct access to edge weights via `neighbor.weight`
- No graph conversion needed
- Uses NetworKit's cache-friendly neighbor iteration

## Summary: Porting Checklist

When porting an algorithm from RustworkxCore/Graphrs to NetworKit:

1. **Replace graph type**: `G` or `Graph<T,A>` → `Graph` (our NetworKit Graph)
2. **Replace node iteration**:
   - `graph.node_identifiers()` → `graph.nodes()`
   - `graph.nodes` → `graph.nodes()`
3. **Replace neighbor iteration**:
   - `graph.neighbors(n)` → `graph.out_neighbors(n)` or `graph.in_neighbors(n)`
   - Access neighbor as `neighbor.target` instead of just `neighbor`
4. **Replace degree calculation**:
   - `graph.neighbors(n).count()` → `graph.degree(n)` (much faster!)
5. **Replace edge weights**:
   - Petgraph: `edge.weight()`
   - NetworKit: `neighbor.weight.unwrap_or(1.0)`
6. **Use our types**:
   - `NodeId` (already defined as `u64`)
   - `Weight` (defined as `f64`)

## Where to Find Source Code

- **RustworkxCore**: https://github.com/Qiskit/rustworkx/tree/main/rustworkx-core/src
- **Graphrs**: https://github.com/malcolmvr/graphrs/tree/main/src/algorithms

## Next Steps

The best approach is:
1. Pick an algorithm (e.g., A*, Louvain, betweenness centrality)
2. Find its source code in RustworkxCore or Graphrs
3. Port it to work directly with our NetworKit `Graph` structure
4. Add it to `src/graph/backends/networkit_rust/algorithms/`
5. No conversions needed - just pure performance!

This is exactly how we've already ported NetworKit's own algorithms (PageRank, BFS, Dijkstra, etc.) - now we're extending the same pattern to import algorithms from other libraries.
