Skip to content

Conversation

dizzyi
Copy link
Contributor

@dizzyi dizzyi commented Aug 6, 2025

Development Workflow

  • make format
  • make test
  • make lint

Features

  • Weighed Eigen Vector Centrality

    Added option to calculate eigen vector centrality with edge weight.
    similar to networkx's api

    eigenvector_centrality(G, max_iter=100, tol=1e-06, nstart=None, weight=None)
    
  • Arbitrary weight evaluation

    Added option to customize edge weight evaluation for eigen vector centrality calculation. This features is hidden behind eigenvector_centrality and only visible in eigenvector_centrality_impl, which allow both clean interface a option to more complex features

  • DocString for eigenvector centrality function

API change

eigenvector(&graph, max_iter)
|
v
eigenvector(&graph, max_iter, weighted /*bool*/)

Examples

use graphina::core::types::Graph;
use graphina::centrality::algorithms::*;

fn main() {
    let mut graph = Graph::new();
    let ids = (0..5).map(|i| graph.add_node(i)).collect::<Vec<_>>();
    let edges = [
        (0, 1, 1.0),
        (0, 2, 2.0),
        (1, 3, 1.0),
    ];
    for (s, d, w) in edges {
        graph.add_edge(ids[s], ids[d], w);
    }

    // // Original
    // let centrality = eigenvector_centrality(&graph, 1000);
    // for (n, attr) in graph.nodes() {
    //     println!(">> {} : {:.5}", attr, centrality[n.index()])
    // }

    // Unweighted Example
    let centrality = eigenvector_centrality(&graph, 1000, false);
    println!("Unweighted:");
    for (n, attr) in graph.nodes() {
        println!(">> {} : {:.5}", attr, centrality[n.index()])
    }

    // Weighted Example
    let centrality = eigenvector_centrality(&graph, 1000, true);
    println!("Weighted:");
    for (n, attr) in graph.nodes() {
        println!(">> {} : {:.5}", attr, centrality[n.index()])
    }
}
Unweighted:
>> 0 : 0.60150
>> 1 : 0.60150
>> 2 : 0.37175
>> 3 : 0.37175
>> 4 : 0.00000
Weighted:
>> 0 : 0.68819
>> 1 : 0.37175
>> 2 : 0.60150
>> 3 : 0.16246
>> 4 : 0.00000

sanity check with networkx

import networkx as nx

G = nx.Graph()

for i in range(5):
    G.add_node(i)

edges = [
    (0, 1, 1.0),
    (0, 2, 2.0),
    (1, 3, 1.0),
]

for src, dst, weight in edges:
    G.add_edge(src, dst, weight=weight)

centrality = nx.eigenvector_centrality(G, weight=None)
print("Unweighted: ")
for v, c in centrality.items():
    print(f">> {v} : {c:.5f}")
centrality = nx.eigenvector_centrality(G, weight="weight")
print("Weighted: ")
for v, c in centrality.items():
    print(f">> {v} : {c:.5f}")

custom evaluation function

use graphina::core::types::Graph;
use graphina::centrality::algorithms::eigenvector_centrality_impl;
let mut g: Graph<i32, (f64, f64)> = Graph::new();
//                    ^^^^^^^^^^
//                             L arbitrary type as edge
let nodes = [g.add_node(1), g.add_node(2), g.add_node(3)];
g.add_edge(nodes[0], nodes[1], (0.0, 1.0));
g.add_edge(nodes[0], nodes[2], (1.0, 0.0));
let centrality = eigenvector_centrality_impl(
    &g, 
    1000, 
    1e-6_f64, 
    |w| w.0 * 10.0 + w.1 // <-- custom evaluation for edge weight
);
println!("{:.5?}", centrality); // [0.70711, 0.07036, 0.70360]

Code Changes

eigenvector_centrality_impl

allow generic type to be edge type, and accept a callback to evalate the weight of the edge.

pub fn eigenvector_centrality_impl<A, W /* any type can be edge */, Ty>(
    graph: &BaseGraph<A, W, Ty>,
    max_iter: usize,
    tol: f64,
    eval_weight: impl Fn(&W) -> f64, // custom function to eval weight
) -> Vec<f64>

calculate the next value with weight.

    // ...
    for (src, dst, w) in graph.edges() {
        let w = eval_weight(w);
        next[dst.index()] += w * centrality[src.index()];
        // if the graph is directed, the backward is not added
        if !<Ty as GraphConstructor<A, W>>::is_directed() {
            next[src.index()] += w * centrality[dst.index()];
        }
    }
    // ...

instead of a new zero vec for next, the next is a clone of centrality, that's how networkx does it

//...
let mut centrality = vec![1.0; n];
let mut next = centrality.clone();
for _ in 0..max_iter {
    //...
    let diff: f64 = centrality
        .iter_mut()
        .zip(next.iter())
        .map(|(a, b)| {
            let d = (*a - b).abs();
            *a = *b; // update `centrality` from `next`
            d
        })
        .sum();
    //...
}

eigenvector_centrality and eigenvector_centrality_numpy

pub fn eigenvector_centrality<A, Ty>(
    graph: &BaseGraph<A, f64, Ty>,
    max_iter: usize,
    weighted: bool,
) -> Vec<f64>
where
    Ty: GraphConstructor<A, f64>,
{
    let eval_weight = if weighted {
        |f: &f64| *f   // weighted edge
    } else {
        |_f: &f64| 1.0 // unweighed, all edge weighted equally
    };
    eigenvector_centrality_impl(graph, max_iter, 1e-6_f64, eval_weight)
}

Copy link

codecov bot commented Aug 6, 2025

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

Thanks for integrating Codecov - We've got you covered ☂️

@habedi habedi self-assigned this Aug 6, 2025
@habedi habedi added enhancement New feature or request development Development-related work labels Aug 6, 2025
@habedi habedi merged commit 9d4886c into habedi:main Aug 6, 2025
6 checks passed
@habedi
Copy link
Owner

habedi commented Aug 6, 2025

Thanks for the impressive work!

habedi pushed a commit that referenced this pull request Aug 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
development Development-related work enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants