tmapper-py 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tmapper/__init__.py +63 -0
- tmapper/_shortest_path.py +22 -0
- tmapper/app/__init__.py +10 -0
- tmapper/app/launcher.py +35 -0
- tmapper/app/streamlit_app.py +1240 -0
- tmapper/cknngraph.py +73 -0
- tmapper/cycle_cluster.py +111 -0
- tmapper/cycle_cluster_conn.py +112 -0
- tmapper/cycle_count.py +150 -0
- tmapper/cycle_count2p.py +72 -0
- tmapper/cycle_cutter.py +47 -0
- tmapper/cycle_overlap.py +71 -0
- tmapper/cycle_path_decomp.py +104 -0
- tmapper/cycles_to_paths.py +40 -0
- tmapper/filtergraph.py +107 -0
- tmapper/graph_utils.py +283 -0
- tmapper/knngraph.py +61 -0
- tmapper/labeling.py +45 -0
- tmapper/modularity.py +74 -0
- tmapper/path_traffic.py +35 -0
- tmapper/plotting.py +463 -0
- tmapper/sample_data.py +19 -0
- tmapper/sampledata/EL_temp.csv +57710 -0
- tmapper/tcm_distance.py +63 -0
- tmapper/tknndigraph.py +190 -0
- tmapper_py-0.1.0.dist-info/METADATA +108 -0
- tmapper_py-0.1.0.dist-info/RECORD +31 -0
- tmapper_py-0.1.0.dist-info/WHEEL +5 -0
- tmapper_py-0.1.0.dist-info/entry_points.txt +2 -0
- tmapper_py-0.1.0.dist-info/licenses/LICENSE +28 -0
- tmapper_py-0.1.0.dist-info/top_level.txt +1 -0
tmapper/tcm_distance.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Port of tmapper_tools/TCMdistance.m from the MATLAB toolbox."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import networkx as nx
|
|
5
|
+
|
|
6
|
+
from ._shortest_path import all_pairs_distance
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def tcm_distance(g, nodet, weighted=False):
|
|
10
|
+
"""Compute a temporal connectivity matrix: for every pair of original
|
|
11
|
+
time points, the shortest path length between the nodes (of a
|
|
12
|
+
simplified graph ``g``) that contain them.
|
|
13
|
+
|
|
14
|
+
Port of MATLAB's ``TCMdistance.m``. Sizes its output by the full range
|
|
15
|
+
covered by ``nodet`` (``max - min + 1``), not just the count of
|
|
16
|
+
distinct points referenced -- so time points genuinely not covered by
|
|
17
|
+
any node in ``nodet`` correctly stay ``NaN`` rather than being silently
|
|
18
|
+
zero-filled.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
g : networkx.Graph or networkx.DiGraph
|
|
23
|
+
The simplified graph.
|
|
24
|
+
nodet : sequence of sequence of int
|
|
25
|
+
``nodet[i]`` gives the original time-point indices belonging to
|
|
26
|
+
node i of ``g`` (in the same node order as ``g.nodes()``).
|
|
27
|
+
weighted : bool, default False
|
|
28
|
+
Whether to use ``g``'s edge weights. If False, every edge is
|
|
29
|
+
treated as weight 1 (hop count).
|
|
30
|
+
|
|
31
|
+
Returns
|
|
32
|
+
-------
|
|
33
|
+
numpy.ndarray, shape (Nt, Nt)
|
|
34
|
+
The temporal connectivity matrix, where Nt = max(nodet) - min(nodet) + 1.
|
|
35
|
+
Entries for time points never covered by any node stay NaN.
|
|
36
|
+
"""
|
|
37
|
+
nodelist = list(g.nodes())
|
|
38
|
+
all_t = np.concatenate([np.asarray(list(nt), dtype=int) for nt in nodet]) if nodet else np.array([], dtype=int)
|
|
39
|
+
if all_t.size == 0:
|
|
40
|
+
return np.zeros((0, 0))
|
|
41
|
+
t_0 = all_t.min()
|
|
42
|
+
Nt = all_t.max() - t_0 + 1
|
|
43
|
+
|
|
44
|
+
distmat = all_pairs_distance(g, nodelist, weight="weight" if weighted else None)
|
|
45
|
+
|
|
46
|
+
tcm = np.full((Nt, Nt), np.nan)
|
|
47
|
+
|
|
48
|
+
n_nodes = len(nodelist)
|
|
49
|
+
idx_lists = [np.asarray(list(nt), dtype=int) - t_0 for nt in nodet]
|
|
50
|
+
|
|
51
|
+
for i in range(n_nodes):
|
|
52
|
+
ii = idx_lists[i]
|
|
53
|
+
tcm[np.ix_(ii, ii)] = 0
|
|
54
|
+
|
|
55
|
+
for i in range(n_nodes):
|
|
56
|
+
for j in range(i + 1, n_nodes):
|
|
57
|
+
ii, jj = idx_lists[i], idx_lists[j]
|
|
58
|
+
block_ij = tcm[np.ix_(ii, jj)]
|
|
59
|
+
tcm[np.ix_(ii, jj)] = np.fmin(block_ij, distmat[i, j])
|
|
60
|
+
block_ji = tcm[np.ix_(jj, ii)]
|
|
61
|
+
tcm[np.ix_(jj, ii)] = np.fmin(block_ji, distmat[j, i])
|
|
62
|
+
|
|
63
|
+
return tcm
|
tmapper/tknndigraph.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""Port of tmapper_tools/tknndigraph.m from the MATLAB toolbox."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import networkx as nx
|
|
5
|
+
from scipy.spatial.distance import cdist
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _percentile_with_inf(values, prct):
|
|
9
|
+
"""Like ``np.percentile`` (linear interpolation method), but robust to
|
|
10
|
+
``inf`` entries. ``np.percentile`` can return NaN when the interpolation
|
|
11
|
+
boundary falls between two ``inf`` values, because its formula computes
|
|
12
|
+
``a + weight * (b - a)`` and ``inf - inf`` is NaN even when ``weight``
|
|
13
|
+
is (mathematically) zero -- 0 * NaN is still NaN, not 0. This matters
|
|
14
|
+
here since D routinely contains inf entries (masked self-loops/temporal
|
|
15
|
+
exclusions) and MATLAB's prctile does not have this problem.
|
|
16
|
+
"""
|
|
17
|
+
sorted_vals = np.sort(np.asarray(values).ravel())
|
|
18
|
+
n = sorted_vals.size
|
|
19
|
+
if n == 0:
|
|
20
|
+
return np.inf
|
|
21
|
+
virtual_idx = (prct / 100.0) * (n - 1)
|
|
22
|
+
lo = int(np.floor(virtual_idx))
|
|
23
|
+
hi = int(np.ceil(virtual_idx))
|
|
24
|
+
if lo == hi:
|
|
25
|
+
return sorted_vals[lo]
|
|
26
|
+
a, b = sorted_vals[lo], sorted_vals[hi]
|
|
27
|
+
if np.isinf(a) and np.isinf(b) and a == b:
|
|
28
|
+
return a
|
|
29
|
+
frac = virtual_idx - lo
|
|
30
|
+
return a + frac * (b - a)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def tknndigraph(
|
|
34
|
+
X_or_D,
|
|
35
|
+
k,
|
|
36
|
+
tidx,
|
|
37
|
+
*,
|
|
38
|
+
reciprocal=True,
|
|
39
|
+
time_exclude_space=True,
|
|
40
|
+
time_exclude_range=1,
|
|
41
|
+
max_neighbor_dist=np.inf,
|
|
42
|
+
max_neighbor_dist_prct=100.0,
|
|
43
|
+
):
|
|
44
|
+
"""Construct a directed graph based on k-nearest neighbors that also
|
|
45
|
+
includes temporal neighbors (t -> t+1 links).
|
|
46
|
+
|
|
47
|
+
Port of MATLAB's ``tknndigraph.m``. Node i of the returned graph
|
|
48
|
+
corresponds to row i of ``X_or_D`` / element i of ``tidx`` (0-indexed,
|
|
49
|
+
unlike the 1-indexed MATLAB original).
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
X_or_D : array_like, shape (N, d) or (N, N)
|
|
54
|
+
Either raw coordinates (N points in d-dim space) or a precomputed
|
|
55
|
+
(N, N) distance matrix. Auto-detected: treated as a distance
|
|
56
|
+
matrix only if square *and* symmetric; otherwise treated as raw
|
|
57
|
+
coordinates and converted to a Euclidean distance matrix.
|
|
58
|
+
k : int
|
|
59
|
+
Max number of spatial neighbors per point. Must be a positive
|
|
60
|
+
integer strictly less than N.
|
|
61
|
+
tidx : array_like of int, shape (N,)
|
|
62
|
+
Time index per point. Two points x, y are temporal neighbors iff
|
|
63
|
+
``tidx[y] == tidx[x] + 1``.
|
|
64
|
+
reciprocal : bool, default True
|
|
65
|
+
Whether to require spatial k-NN links to be mutual (both
|
|
66
|
+
directions) to be kept.
|
|
67
|
+
time_exclude_space : bool, default True
|
|
68
|
+
Whether temporal neighbors are barred from also counting as
|
|
69
|
+
spatial neighbors.
|
|
70
|
+
time_exclude_range : int, default 1
|
|
71
|
+
How many following time points are excluded from spatial-kNN
|
|
72
|
+
consideration.
|
|
73
|
+
max_neighbor_dist : float, default inf
|
|
74
|
+
Absolute distance cutoff for spatial neighbors.
|
|
75
|
+
max_neighbor_dist_prct : float, default 100
|
|
76
|
+
Percentile distance cutoff for spatial neighbors. The stricter
|
|
77
|
+
(smaller) of this and ``max_neighbor_dist`` is applied.
|
|
78
|
+
|
|
79
|
+
Returns
|
|
80
|
+
-------
|
|
81
|
+
g : networkx.DiGraph
|
|
82
|
+
Unweighted directed graph, one node per input point.
|
|
83
|
+
par : dict
|
|
84
|
+
Parameters actually used, including the resolved
|
|
85
|
+
``max_neighbor_dist``.
|
|
86
|
+
"""
|
|
87
|
+
X_or_D = np.asarray(X_or_D, dtype=float)
|
|
88
|
+
if X_or_D.ndim != 2:
|
|
89
|
+
raise ValueError("X_or_D must be a 2D array.")
|
|
90
|
+
if not (np.isscalar(k) and float(k) == round(float(k)) and k >= 1):
|
|
91
|
+
raise ValueError("k must be a positive integer scalar.")
|
|
92
|
+
k = int(k)
|
|
93
|
+
|
|
94
|
+
tidx = np.asarray(tidx, dtype=float).ravel()
|
|
95
|
+
if tidx.shape[0] != X_or_D.shape[0]:
|
|
96
|
+
raise ValueError(
|
|
97
|
+
"tidx must have the same number of elements as rows of X_or_D."
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
par = {
|
|
101
|
+
"reciprocal": reciprocal,
|
|
102
|
+
"time_exclude_space": time_exclude_space,
|
|
103
|
+
"time_exclude_range": time_exclude_range,
|
|
104
|
+
"max_neighbor_dist": max_neighbor_dist,
|
|
105
|
+
"max_neighbor_dist_prct": max_neighbor_dist_prct,
|
|
106
|
+
"k": k,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# -- check input and obtain distance matrix D
|
|
110
|
+
nr, nc = X_or_D.shape
|
|
111
|
+
if nr != nc or not np.allclose(X_or_D, X_or_D.T):
|
|
112
|
+
D = cdist(X_or_D, X_or_D)
|
|
113
|
+
else:
|
|
114
|
+
D = X_or_D.copy()
|
|
115
|
+
Nn = D.shape[0]
|
|
116
|
+
|
|
117
|
+
if k >= Nn:
|
|
118
|
+
raise ValueError(f"k must be smaller than the number of points ({Nn}).")
|
|
119
|
+
|
|
120
|
+
np.fill_diagonal(D, np.inf) # exclude self-loops
|
|
121
|
+
|
|
122
|
+
# -- find indices for temporal links D[i(t), i(t+1)]
|
|
123
|
+
# t_wafter[i] is True iff point i has a point immediately after it in time
|
|
124
|
+
# (mirrors MATLAB's circshift(tidx,-1,1) - 1 == tidx, including its
|
|
125
|
+
# wraparound behavior at the last index, which is virtually always False
|
|
126
|
+
# for non-cyclic tidx and is harmless here for the same reason it is in
|
|
127
|
+
# the original).
|
|
128
|
+
tidx_next = np.roll(tidx, -1)
|
|
129
|
+
t_wafter = tidx_next - 1 == tidx
|
|
130
|
+
|
|
131
|
+
# t_after_idx1: unconditional immediate temporal edges i -> i+1
|
|
132
|
+
t_after_idx1 = np.zeros((Nn, Nn), dtype=bool)
|
|
133
|
+
for i in range(Nn):
|
|
134
|
+
if t_wafter[i]:
|
|
135
|
+
t_after_idx1[i, (i + 1) % Nn] = True
|
|
136
|
+
|
|
137
|
+
# t_after_idx: temporal-exclusion mask, extended up to time_exclude_range
|
|
138
|
+
# steps, restricted to strictly-forward (i < j) positions
|
|
139
|
+
t_after_idx = np.zeros((Nn, Nn), dtype=bool)
|
|
140
|
+
for n in range(1, time_exclude_range + 1):
|
|
141
|
+
for i in range(Nn):
|
|
142
|
+
if t_wafter[i]:
|
|
143
|
+
t_after_idx[i, (i + n) % Nn] = True
|
|
144
|
+
upper = np.triu(np.ones((Nn, Nn), dtype=bool), k=1)
|
|
145
|
+
t_after_idx = t_after_idx & upper
|
|
146
|
+
|
|
147
|
+
if time_exclude_space:
|
|
148
|
+
D[t_after_idx] = np.inf
|
|
149
|
+
|
|
150
|
+
# -- compute adjacency matrix (k nearest neighbors per row)
|
|
151
|
+
order = np.argsort(D, axis=1, kind="stable")
|
|
152
|
+
D_sorted = np.take_along_axis(D, order, axis=1)
|
|
153
|
+
A = np.zeros((Nn, Nn), dtype=bool)
|
|
154
|
+
rows = np.repeat(np.arange(Nn), k)
|
|
155
|
+
cols = order[:, :k].ravel()
|
|
156
|
+
A[rows, cols] = True
|
|
157
|
+
|
|
158
|
+
# -- check for duplicate points: any other point tied with the k-th
|
|
159
|
+
# nearest neighbor's distance is also included
|
|
160
|
+
dmax = D_sorted[:, k - 1][:, None]
|
|
161
|
+
A |= D <= dmax
|
|
162
|
+
|
|
163
|
+
# -- get distance threshold (the stricter of the absolute and percentile caps).
|
|
164
|
+
# NOTE: matches MATLAB's prctile(D(:), Prct) exactly by computing the
|
|
165
|
+
# percentile over the *full* D, Inf entries included (the masked
|
|
166
|
+
# diagonal/temporal-exclusion entries) -- not just the finite values.
|
|
167
|
+
prct_dist = _percentile_with_inf(D, max_neighbor_dist_prct)
|
|
168
|
+
resolved_max_dist = min(prct_dist, max_neighbor_dist)
|
|
169
|
+
par["max_neighbor_dist"] = resolved_max_dist
|
|
170
|
+
|
|
171
|
+
# -- remove neighbors that exceed max distance
|
|
172
|
+
A[D > resolved_max_dist] = False
|
|
173
|
+
|
|
174
|
+
# -- exclude or retain temporal links as spatial links
|
|
175
|
+
if time_exclude_space:
|
|
176
|
+
A_space = A & ~t_after_idx
|
|
177
|
+
else:
|
|
178
|
+
A_space = A.copy()
|
|
179
|
+
|
|
180
|
+
# -- enforce symmetry of spatial links
|
|
181
|
+
if reciprocal:
|
|
182
|
+
A_space = A_space & A_space.T
|
|
183
|
+
else:
|
|
184
|
+
A_space = A_space | A_space.T
|
|
185
|
+
|
|
186
|
+
# -- (re-)incorporate temporal links
|
|
187
|
+
A_final = t_after_idx1 | A_space
|
|
188
|
+
|
|
189
|
+
g = nx.from_numpy_array(A_final, create_using=nx.DiGraph)
|
|
190
|
+
return g, par
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tmapper-py
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python port of Temporal Mapper 2 -- build attractor transition networks from time-series data
|
|
5
|
+
Author: Mengsen Zhang
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://github.com/Multiscale-Complex-Systems-Lab/tmapper-py
|
|
8
|
+
Project-URL: MATLAB original, https://github.com/Multiscale-Complex-Systems-Lab/tmapper2
|
|
9
|
+
Keywords: temporal mapper,topological data analysis,mapper,dynamical systems,time series,attractor,recurrence
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: numpy>=1.24
|
|
23
|
+
Requires-Dist: scipy>=1.10
|
|
24
|
+
Requires-Dist: networkx>=3.0
|
|
25
|
+
Provides-Extra: plot
|
|
26
|
+
Requires-Dist: matplotlib>=3.7; extra == "plot"
|
|
27
|
+
Requires-Dist: python-igraph>=1.0; extra == "plot"
|
|
28
|
+
Requires-Dist: pyvis>=0.3; extra == "plot"
|
|
29
|
+
Provides-Extra: app
|
|
30
|
+
Requires-Dist: streamlit>=1.30; extra == "app"
|
|
31
|
+
Requires-Dist: pandas>=1.5; extra == "app"
|
|
32
|
+
Requires-Dist: matplotlib>=3.7; extra == "app"
|
|
33
|
+
Requires-Dist: python-igraph>=1.0; extra == "app"
|
|
34
|
+
Requires-Dist: pyvis>=0.3; extra == "app"
|
|
35
|
+
Provides-Extra: test
|
|
36
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
37
|
+
Requires-Dist: pandas>=1.5; extra == "test"
|
|
38
|
+
Provides-Extra: docs
|
|
39
|
+
Requires-Dist: mkdocs-material>=9.5; extra == "docs"
|
|
40
|
+
Requires-Dist: mkdocstrings[python]>=0.25; extra == "docs"
|
|
41
|
+
Dynamic: license-file
|
|
42
|
+
|
|
43
|
+
# tmapper (Python)
|
|
44
|
+
|
|
45
|
+
A Python port of [Temporal Mapper 2](https://github.com/Multiscale-Complex-Systems-Lab/tmapper2), a toolbox for building **attractor transition networks** from time-series data.
|
|
46
|
+
|
|
47
|
+
**Status: feature-complete port.** Both the core two-step pipeline (`tknndigraph` + `filtergraph` + plotting) and the secondary cycle/path analysis toolkit (cycle counting/clustering, path decomposition, traffic, modularity) are ported and tested. `tknngraph.m` (a legacy, unused MATLAB function) was intentionally not ported.
|
|
48
|
+
|
|
49
|
+
For the full background, citation, and the original MATLAB implementation, see [tmapper2](https://github.com/Multiscale-Complex-Systems-Lab/tmapper2).
|
|
50
|
+
|
|
51
|
+
**Documentation, quickstart, and full API reference:** https://multiscale-complex-systems-lab.github.io/tmapper-py/
|
|
52
|
+
|
|
53
|
+
## What's included
|
|
54
|
+
|
|
55
|
+
- **Core pipeline**: `tknndigraph`, `filtergraph`, `find_node_label`, `tcm_distance`, `plot_tmgraph`, `plot_tmgraph_tcm`, `plot_tmgraph_interactive` (draggable/zoomable/hoverable HTML via pyvis, no MATLAB equivalent)
|
|
56
|
+
- **Standalone graph builders**: `knngraph`, `cknngraph`
|
|
57
|
+
- **Graph/data utilities**: `node_size`, `node_measure`, `normalize_geodesic`, `normalize_tcm`, `members_to_tidx`, `subgraph_from_members`, `sym_dyn_to_digraph`, `digraph_to_graph`, `find_blocks`
|
|
58
|
+
- **Cycle/path analysis toolkit**: `cycle_count` (Giscard/Kriege/Wilson combinatorial-sieve counter, third-party algorithm carrying its own BSD license -- see `cycle_count.py`), `cycle_count2p`, `reorg_cycles`, `cycle_path_overlap`, `cycle_cluster`, `cycle_cluster_conn`, `cycle_cutter`, `cycles_to_paths`, `cycle_path_decomp`, `path_traffic`, `qasym`, `cal_mod`
|
|
59
|
+
- **Interactive app**: `tmapper-app`, a browser front end for the whole pipeline -- see below
|
|
60
|
+
|
|
61
|
+
All ported functions were cross-checked node-for-node and edge-for-edge against real MATLAB output on deterministic test graphs (see the test suite for the same hand-derived oracle values used in the MATLAB toolbox's own `tests/`).
|
|
62
|
+
|
|
63
|
+
## Installation
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install tmapper-py # library
|
|
67
|
+
pip install "tmapper-py[app]" # library + the interactive app
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
(The distribution is `tmapper-py` -- `tmapper` alone isn't available on PyPI,
|
|
71
|
+
see [Installation](https://multiscale-complex-systems-lab.github.io/tmapper-py/installation/).
|
|
72
|
+
Everything imports as `import tmapper` either way.)
|
|
73
|
+
|
|
74
|
+
For development, from a clone:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
git clone https://github.com/Multiscale-Complex-Systems-Lab/tmapper-py.git
|
|
78
|
+
cd tmapper-py
|
|
79
|
+
pip install -e ".[app,test]"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Interactive app
|
|
83
|
+
|
|
84
|
+
Prefer point-and-click? `tmapper-app` runs the same pipeline in your browser -- load data, pick variables, turn the parameters, and explore the network without writing any code:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pip install "tmapper-py[app]"
|
|
88
|
+
tmapper-app
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
It handles missing data and downsampling for you (dropping incomplete rows before an anti-aliasing lowpass), exports both figures and analysis-ready data (interactive HTML, PNGs, a per-timepoint `timeline.csv`, GraphML, and a provenance JSON), and shows a runnable script reproducing the current build.
|
|
92
|
+
|
|
93
|
+
See the [Interactive App guide](https://multiscale-complex-systems-lab.github.io/tmapper-py/app/) for a full walkthrough.
|
|
94
|
+
|
|
95
|
+
## Running tests
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
pytest
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Notes for users porting workflows from MATLAB
|
|
102
|
+
|
|
103
|
+
- Node labels are 0-indexed (Pythonic), unlike the MATLAB original's 1-indexed convention.
|
|
104
|
+
- If you z-score your data before calling `tknndigraph` (as `tmapper_demo.m` does), note that MATLAB's `zscore` divides by the sample standard deviation (`N-1`), while `scipy.stats.zscore` defaults to the population standard deviation (`N`). Pass `ddof=1` to `scipy.stats.zscore` to match MATLAB's convention -- otherwise results can subtly diverge near k-NN/threshold boundaries. (Verified: with `ddof=1`, the Python and MATLAB pipelines produce identical output down to floating-point precision on the sample dataset.)
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
BSD 3-Clause License -- see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
tmapper/__init__.py,sha256=-2Jqu7rvO_C-WX4zo0wfmEXZSOx2RSUpDnYy-dtJFa8,1676
|
|
2
|
+
tmapper/_shortest_path.py,sha256=TRyUOHOuizrMYWnsoy_SVCU2aL4Q1hcp41EO1KiuhcU,1036
|
|
3
|
+
tmapper/cknngraph.py,sha256=Wamujr4i6h7fiCvm23yPL9whXXs5FbFEMghXmdjjxp8,2412
|
|
4
|
+
tmapper/cycle_cluster.py,sha256=5EbTgUKEXnVexRf5g9LK3uSSkJXxmRbN2MdfMPk8TrA,4089
|
|
5
|
+
tmapper/cycle_cluster_conn.py,sha256=1yvj7ksr2OODQZaVTyZI5DO11mQ-1ahvPIcOtPCmw3I,4263
|
|
6
|
+
tmapper/cycle_count.py,sha256=ZMs4pT7xZnmKmQpE-to9UL0NIiwam53j1I27935TlUI,5640
|
|
7
|
+
tmapper/cycle_count2p.py,sha256=TujH-xuqt4NHu-p36x1BQUOP7VkUmsWTKb-38fXfWww,2570
|
|
8
|
+
tmapper/cycle_cutter.py,sha256=AQtHRYTACyXK9muW_NINiH_CSIAia4MmvvSaB2KuVmg,1341
|
|
9
|
+
tmapper/cycle_overlap.py,sha256=dVOB952P2CX5wadm0rKqR-Nvc5kNhxcDxqVMfCa2BEo,2180
|
|
10
|
+
tmapper/cycle_path_decomp.py,sha256=QUYcOFZ3x-KOErxtn24dVhkR0NmkUteYvy6LGXZKU6A,4446
|
|
11
|
+
tmapper/cycles_to_paths.py,sha256=VNj1ipbd6OSRv2GK1Y4kBE4fyMj1RU_qu39pESaNF0I,1072
|
|
12
|
+
tmapper/filtergraph.py,sha256=L17Bp6OU3MdcFm6ObUP9E726sT8qZZ-2i9LdlCyM79Y,4408
|
|
13
|
+
tmapper/graph_utils.py,sha256=KY5ULQdk_l1GHfU18ilwdhgDZZZN0Ce9qUpJ47qcGZ4,9294
|
|
14
|
+
tmapper/knngraph.py,sha256=HxEyl2xU8LVg25i1jE0A3LFTSSUgF1OoyoTQRggqoo4,1941
|
|
15
|
+
tmapper/labeling.py,sha256=f1dZr-9yZcYtga0ZGC0NypQ1KiSgJm6EXxEZ4oCsLAI,1696
|
|
16
|
+
tmapper/modularity.py,sha256=HMlALb9QNXwpgPu4UYvF8KAdIvfnzIYj9Uscd4wWuFA,2029
|
|
17
|
+
tmapper/path_traffic.py,sha256=KnVKwirP3hCZKxCKqnNeqN2w5L7qnU80ZeiBhRg_QUU,1262
|
|
18
|
+
tmapper/plotting.py,sha256=e_wZ3xND_KLEMV9hP4uWHb_hJaGe6gHtGZyHrAu_uDU,17669
|
|
19
|
+
tmapper/sample_data.py,sha256=ODY_TKb95N6Bagaw75YMPIwbFjD9GZA5-MXdkFN1WmQ,715
|
|
20
|
+
tmapper/tcm_distance.py,sha256=yvp99IRaBzXy1v_7PFd2ZsuWBQhkkpHZdsn3Nzkad1Q,2268
|
|
21
|
+
tmapper/tknndigraph.py,sha256=M3aASlUW3K3_rE5OsTJ2KfhQQ0vpGAvg0Xz8ph-D9vE,7020
|
|
22
|
+
tmapper/app/__init__.py,sha256=5APA90YXLvorLyJayo6wQ2XsroXfbuQX-Q6B5EsoEBw,305
|
|
23
|
+
tmapper/app/launcher.py,sha256=3JT3uw1AzL8Ob6tJ01ogDGUh5hUWUNWbCLko0mi2_zQ,1127
|
|
24
|
+
tmapper/app/streamlit_app.py,sha256=9dSPvVjSi6xdOYXr1pOlGWJmUAOQWvYHM8nfldFYl5M,55962
|
|
25
|
+
tmapper/sampledata/EL_temp.csv,sha256=x8d1psZtkBhCagScJ6tdXZn_RxNyku5For6vwo6j2mA,1605205
|
|
26
|
+
tmapper_py-0.1.0.dist-info/licenses/LICENSE,sha256=PrN1LOSoJpxEvEWu6ZqsD_haOJDbD7oY-U23XdiAVw4,1517
|
|
27
|
+
tmapper_py-0.1.0.dist-info/METADATA,sha256=pR7DSIcXc66jZj_mDdNXhdpxMxc10RpKUdD6FRZbrVI,5769
|
|
28
|
+
tmapper_py-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
29
|
+
tmapper_py-0.1.0.dist-info/entry_points.txt,sha256=37FDwhSH65AcTarmiBtrHV1ftPXMLnKzE4V7HSamDmA,58
|
|
30
|
+
tmapper_py-0.1.0.dist-info/top_level.txt,sha256=rCHRsCknm1Ae25rp11L0fu_mQP7qcF7hUhgOZtNW5PM,8
|
|
31
|
+
tmapper_py-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Multiscale Complex Systems Lab
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
tmapper
|