spatial_graph_algorithms.reconstruct
Recover 2-D or 3-D node coordinates from graph topology alone.
Available methods
| Method | Algorithm | When to use |
|---|---|---|
mds |
All-pairs shortest-path distances → sklearn metric MDS | Fast baseline; no extra dependencies |
landmark_mds |
Landmark shortest-path distances → MDS + triangulation | Faster approximation when all-pairs MDS is too expensive |
strnd |
Node2Vec (pecanpy PreComp) → UMAP | State-of-the-art quality; included in base install |
gse |
Geodesic Spectral Embedding | Bipartite graphs; requires the gse extra |
quantum_interferometry |
Bipartite resolvent responses → randomized SVD → UMAP | Experimental bipartite reconstruction |
How to choose
- Use MDS for quick checks or small graphs (< 500 nodes).
- Use landmark MDS when you want a seeded MDS-style approximation and can choose an explicit landmark count.
- Use STRND for production-quality reconstructions. It consistently achieves CPD > 0.95 on well-connected circle/square graphs.
- Use GSE or quantum interferometry only for bipartite graphs.
Quantum interferometry is experimental and returns
NaNrows for isolated nodes, matching the input graph node order. Its tuned defaults areomega=0.2,gamma=0.25, andn_probes=None(all non-isolated nodes).
Quality evaluation
After reconstruction, use spatial_graph_algorithms.metrics.evaluate() to score the result.
See the metrics reference for the interpretation guide.
API Reference
spatial_graph_algorithms.reconstruct.reconstruct(sg, method='mds', dim=2, seed=None, **kwargs)
Reconstruct node coordinates from graph topology.
Returns a copy of sg with
:attr:~spatial_graph_algorithms.network.SpatialGraph.reconstructed_positions
set. The original sg is never modified.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sg
|
SpatialGraph
|
Input graph. Only the adjacency matrix is used; positions are not required (they are used only for quality evaluation afterwards). |
required |
method
|
str
|
Reconstruction algorithm. One of:
|
'mds'
|
dim
|
int
|
Target number of output dimensions (2 or 3). |
2
|
seed
|
int
|
Random seed for reproducibility. |
None
|
Returns:
| Type | Description |
|---|---|
SpatialGraph
|
Deep copy of sg with reconstructed_positions of shape
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If method is not one of the supported values. |
Examples:
>>> from spatial_graph_algorithms.simulate import generate
>>> from spatial_graph_algorithms.reconstruct import reconstruct
>>> sg = generate(n=300, seed=42)
>>> sg_rec = reconstruct(sg, method="mds", dim=2, seed=42)
>>> sg_rec.reconstructed_positions.shape
(300, 2)
Source code in src/spatial_graph_algorithms/reconstruct/__init__.py
spatial_graph_algorithms.reconstruct.mds.run_mds(adjacency, dim=2, random_state=None)
Reconstruct node coordinates using metric MDS on shortest-path distances.
Computes all-pairs shortest-path distances from adjacency and passes the
resulting dissimilarity matrix to sklearn's MDS with
dissimilarity='precomputed'. Disconnected pairs receive twice the
maximum finite distance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
adjacency
|
scipy.sparse matrix
|
Symmetric, unweighted adjacency matrix of shape |
required |
dim
|
int
|
Number of output dimensions. Default is 2. |
2
|
random_state
|
int
|
Seed for sklearn's MDS initialisation. |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Float array of shape |
Examples:
>>> from spatial_graph_algorithms.simulate import generate
>>> from spatial_graph_algorithms.reconstruct.mds import run_mds
>>> sg = generate(n=100, seed=0)
>>> coords = run_mds(sg.adjacency_matrix, dim=2, random_state=0)
>>> coords.shape
(100, 2)
Source code in src/spatial_graph_algorithms/reconstruct/mds.py
spatial_graph_algorithms.reconstruct.landmark_mds.run_landmark_mds(adjacency, dim=2, random_state=None, *, n_landmarks=None, weighted=False)
Reconstruct coordinates using landmark MDS.
Selects n_landmarks random nodes, computes shortest-path distances from
those landmarks to every node, embeds the landmarks with metric MDS, and
triangulates every node from its distances to the landmark set.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
adjacency
|
scipy.sparse matrix
|
Symmetric adjacency matrix of shape |
required |
dim
|
int
|
Number of output dimensions. Default is 2. |
2
|
random_state
|
int
|
Seed for landmark selection and sklearn's MDS initialisation. |
None
|
n_landmarks
|
int
|
Number of landmark nodes. Must satisfy |
None
|
weighted
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Float array of shape |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
>>> from spatial_graph_algorithms.simulate import generate
>>> from spatial_graph_algorithms.reconstruct.landmark_mds import run_landmark_mds
>>> sg = generate(n=100, seed=0)
>>> coords = run_landmark_mds(sg.adjacency_matrix, dim=2, random_state=0, n_landmarks=16)
>>> coords.shape
(100, 2)
Source code in src/spatial_graph_algorithms/reconstruct/landmark_mds.py
spatial_graph_algorithms.reconstruct.strnd.run_strnd(adjacency, dim=2, random_state=None, embedding_dim=64, p=1.0, q=1.0, workers=4, umap_n_neighbors=15, umap_min_dist=1.0, verbose=False)
Reconstruct coordinates using STRND: Node2Vec embeddings → UMAP.
Exact port of the NSC compute_pecanpy_embeddings_from_df + UMAP pipeline.
Node2Vec biased random walks (pecanpy PreComp) produce a 64-D embedding which
UMAP reduces to dim spatial coordinates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
adjacency
|
scipy.sparse matrix
|
Symmetric, unweighted adjacency matrix. |
required |
dim
|
int
|
Number of output dimensions. Default is 2. |
2
|
random_state
|
int
|
Seed for UMAP reproducibility. |
None
|
embedding_dim
|
int
|
Dimensionality of the Node2Vec embedding before UMAP reduction. Default is 64 (matches NSC defaults). |
64
|
p
|
float
|
Node2Vec return parameter. |
1.0
|
q
|
float
|
Node2Vec in-out parameter. |
1.0
|
workers
|
int
|
Number of worker threads for pecanpy. Default is 4. |
4
|
umap_n_neighbors
|
int
|
UMAP |
15
|
umap_min_dist
|
float
|
UMAP |
1.0
|
verbose
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Float array of shape |
Examples:
>>> from spatial_graph_algorithms.simulate import generate
>>> from spatial_graph_algorithms.reconstruct.strnd import run_strnd
>>> sg = generate(n=200, seed=0)
>>> coords = run_strnd(sg.adjacency_matrix, dim=2, random_state=0)
>>> coords.shape
(200, 2)
Source code in src/spatial_graph_algorithms/reconstruct/strnd.py
spatial_graph_algorithms.reconstruct.quantum_interferometry.run_quantum_interferometry(graph, dim=2, random_state=None, *, batch_size=64, omega=0.2, gamma=0.25, n_probes=None, weighted=False, svd_components=32, svd_oversamples=10, svd_iterations=4, umap_n_neighbors=15, umap_min_dist=1.0, umap_metric='cosine', umap_epochs=500, umap_jobs=1)
Reconstruct coordinates using bipartite quantum interferometry.
The input graph must be bipartite. The two node partitions are inferred from the graph structure. The method builds a normalized bipartite interaction block, evaluates batched resolvent responses, embeds the complex responses with randomized SVD, and reduces the result with UMAP.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
graph
|
Graph
|
Bipartite graph to reconstruct. Output rows follow |
required |
dim
|
int
|
Number of output dimensions. Default is 2. |
2
|
random_state
|
int
|
Random seed used for probe sampling, randomized SVD, and UMAP. |
None
|
batch_size
|
int
|
Number of probe vectors to process per resolvent batch. Default is 64. |
64
|
omega
|
float
|
Resolvent frequency parameter. Default is 0.2. |
0.2
|
gamma
|
float
|
Positive imaginary damping parameter. Default is 0.25. |
0.25
|
n_probes
|
int
|
Number of localized probe vectors. If omitted, every node is probed. |
None
|
weighted
|
bool
|
If |
False
|
svd_components
|
int
|
Number of randomized SVD components before UMAP. Default is 32. |
32
|
svd_oversamples
|
int
|
Additional random vectors used by randomized SVD. Default is 10. |
10
|
svd_iterations
|
int
|
Number of randomized SVD power iterations. Default is 4. |
4
|
umap_n_neighbors
|
int
|
UMAP neighborhood size. Default is 15. |
15
|
umap_min_dist
|
float
|
UMAP minimum distance. Default is 1.0. |
1.0
|
umap_metric
|
str
|
UMAP metric. Default is |
'cosine'
|
umap_epochs
|
int
|
Number of UMAP training epochs. Default is 500. |
500
|
umap_jobs
|
int
|
Number of UMAP worker threads. Default is 1. |
1
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Float array of shape |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the graph is empty, not bipartite, or if a numeric parameter is outside its valid range. |
Examples:
>>> import networkx as nx
>>> from spatial_graph_algorithms.reconstruct.quantum_interferometry import (
... run_quantum_interferometry,
... )
>>> graph = nx.Graph()
>>> graph.add_edges_from([("A0", "B0"), ("A1", "B0"), ("A1", "B1")])
>>> coords = run_quantum_interferometry(
... graph,
... dim=2,
... random_state=0,
... batch_size=2,
... n_probes=4,
... svd_components=2,
... umap_n_neighbors=2,
... umap_epochs=20,
... )
>>> coords.shape
(4, 2)
Source code in src/spatial_graph_algorithms/reconstruct/quantum_interferometry.py
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | |
spatial_graph_algorithms.reconstruct.quality
Reconstruction quality metrics comparing original and reconstructed positions.
All three metrics are rotation-, reflection-, and translation-invariant.
Functions:
cpd(true_positions, recon_positions)
Compute the Correlation of Pairwise Distances (CPD).
R² of the pairwise distance matrices of the original and reconstructed coordinates. CPD = 1.0 means all inter-node distances are perfectly preserved.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
true_positions
|
ndarray
|
Ground-truth node coordinates, shape |
required |
recon_positions
|
ndarray
|
Reconstructed node coordinates, shape |
required |
Returns:
| Type | Description |
|---|---|
float
|
R² in |
Examples:
>>> import numpy as np
>>> from spatial_graph_algorithms.reconstruct.quality import cpd
>>> pts = np.random.default_rng(0).random((50, 2))
>>> cpd(pts, pts + 0.001) > 0.99
True
Source code in src/spatial_graph_algorithms/reconstruct/quality.py
knn(true_positions, recon_positions, k=10)
Compute k-nearest-neighbour overlap.
For each node, the fraction of its k true nearest neighbours that also appear among its k reconstructed nearest neighbours, averaged over all nodes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
true_positions
|
ndarray
|
Ground-truth coordinates, shape |
required |
recon_positions
|
ndarray
|
Reconstructed coordinates, shape |
required |
k
|
int
|
Number of neighbours to compare. Clamped to |
10
|
Returns:
| Type | Description |
|---|---|
float
|
Mean neighbourhood overlap in |
Examples:
>>> import numpy as np
>>> from spatial_graph_algorithms.reconstruct.quality import knn
>>> pts = np.random.default_rng(0).random((50, 2))
>>> knn(pts, pts)
1.0
Source code in src/spatial_graph_algorithms/reconstruct/quality.py
distortion(true_positions, recon_positions)
Compute normalised pairwise-distance distortion.
Scale-aligns the reconstructed distances to the ground-truth mean scale,
then returns median(|d_true − d_recon_scaled|) / max(d_true).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
true_positions
|
ndarray
|
Ground-truth coordinates, shape |
required |
recon_positions
|
ndarray
|
Reconstructed coordinates, shape |
required |
Returns:
| Type | Description |
|---|---|
float
|
Normalised distortion in |
Examples:
>>> import numpy as np
>>> from spatial_graph_algorithms.reconstruct.quality import distortion
>>> pts = np.random.default_rng(0).random((50, 2))
>>> distortion(pts, pts)
0.0