Rewire identifies structural changes between two graphs by detecting which nodes have altered their network context and which remain stable.
Given two graphs over a shared node set:
- Learn node embeddings independently
- Align embedding spaces
- Measure per-node displacement
- Quantify neighborhood change
- Cluster the most shifted nodes
-
Load graphs from weighted edge list CSVs
-
Embed nodes independently using Node2Vec (random walks + skip-gram with negative sampling)
-
Align embeddings using Procrustes alignment (centering + optimal rotation)
-
Compute delta vectors (aligned graph2 − graph1)
-
Compute per-node metrics:
- Delta L2: magnitude of displacement in embedding space
- Neighbor Jaccard: overlap of k-nearest neighbors (Annoy angular distance)
- Density shift: mean neighbor distance(graph1) − mean neighbor distance(graph2)
-
Cluster top N nodes using agglomerative clustering on delta vectors
-
Write results to CSV for downstream analysis
- Go 1.22+
- SWIG 4.2+ (with Go support)
- g++
- git (for Annoy bindings)
git clone https://github.com/dfrll/rewire
cd rewire
bash build.shThe build script:
- Builds Annoy bindings
- Compiles the binary
Each graph is a weighted edge list CSV with a header:
source,target,weight
nodeA,nodeB,0.85
nodeA,nodeC,0.72
nodeB,nodeD,0.91
Notes:
- Graph is treated as undirected
- Only nodes present in both graphs are analyzed
cp config.example.yaml config.yaml./rewire embed
./rewire embed --force./rewire diff
./rewire diff --force./rewire query NODE --graph graph1 --n 10./rewire query --batch node_labels.txt --graph graph2 --n 50All parameters can be set via config.yaml or CLI flags.
| Parameter | Description |
|---|---|
graph1 |
Baseline graph |
graph2 |
Comparison graph |
workdir |
Cache + output directory |
force |
Recompute cached artifacts |
nTrees |
Annoy trees (accuracy vs build time) |
| Parameter | Default | Description |
|---|---|---|
seed |
0 | random seed: set to a non-zero value for reproducibility |
dim |
64 | Embedding dimension |
walkLength |
80 | Walk length |
nWalks |
20 | Walks per node |
p |
1.0 | Return parameter |
q |
2.0 | In-out parameter |
restartProbability |
0.02 | Random walk restart probability |
t |
1e-5 | Subsampling threshold |
window |
3 | Context window |
epochs |
50 | Training epochs |
negSamples |
5 | Negative samples per positive |
initLR |
1e-1 | Initial learning rate |
minLR |
1e-3 | Minimum learning rate |
convergenceThreshold |
1e-5 | Early stopping threshold |
nRuns |
1 | Runs to average embeddings over |
writeRuns |
false | Write per-run embeddings to disk |
| Parameter | Default | Description |
|---|---|---|
numNeighbors |
50 | KNN size for metrics |
cutThreshold |
0 | 0 = adaptive threshold |
linkage |
average | Agglomerative linkage |
distance |
cosine | Distance metric |
topN |
20 | Number of most shifted nodes (by delta_l2) to cluster (0 = all) |
All outputs are written to workdir.
| File | Description |
|---|---|
all_nodes.csv |
Metrics for all shared nodes |
nodes.csv |
Metrics + cluster assignments (top N) |
distances.csv |
Merge distances from clustering |
delta.embeddings.csv |
Delta vectors |
| Column | Description |
|---|---|
node |
Node ID |
module |
Cluster assignment (delta space) |
delta_l2 |
Displacement magnitude |
neighbor_jaccard |
Neighbor overlap |
density_shift |
Change in mean neighbor distance |
- High delta_l2 + low Jaccard - strong rewiring
- Low delta_l2 + high Jaccard - stable node
- Positive density_shift - neighborhood becomes tighter in graph2
Stored in workdir:
.emb.gob- embeddings.ann.idx- Annoy indices
MIT