tmapper-py 0.1.0__tar.gz

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.
Files changed (46) hide show
  1. tmapper_py-0.1.0/LICENSE +28 -0
  2. tmapper_py-0.1.0/PKG-INFO +108 -0
  3. tmapper_py-0.1.0/README.md +66 -0
  4. tmapper_py-0.1.0/pyproject.toml +72 -0
  5. tmapper_py-0.1.0/setup.cfg +4 -0
  6. tmapper_py-0.1.0/src/tmapper/__init__.py +63 -0
  7. tmapper_py-0.1.0/src/tmapper/_shortest_path.py +22 -0
  8. tmapper_py-0.1.0/src/tmapper/app/__init__.py +10 -0
  9. tmapper_py-0.1.0/src/tmapper/app/launcher.py +35 -0
  10. tmapper_py-0.1.0/src/tmapper/app/streamlit_app.py +1240 -0
  11. tmapper_py-0.1.0/src/tmapper/cknngraph.py +73 -0
  12. tmapper_py-0.1.0/src/tmapper/cycle_cluster.py +111 -0
  13. tmapper_py-0.1.0/src/tmapper/cycle_cluster_conn.py +112 -0
  14. tmapper_py-0.1.0/src/tmapper/cycle_count.py +150 -0
  15. tmapper_py-0.1.0/src/tmapper/cycle_count2p.py +72 -0
  16. tmapper_py-0.1.0/src/tmapper/cycle_cutter.py +47 -0
  17. tmapper_py-0.1.0/src/tmapper/cycle_overlap.py +71 -0
  18. tmapper_py-0.1.0/src/tmapper/cycle_path_decomp.py +104 -0
  19. tmapper_py-0.1.0/src/tmapper/cycles_to_paths.py +40 -0
  20. tmapper_py-0.1.0/src/tmapper/filtergraph.py +107 -0
  21. tmapper_py-0.1.0/src/tmapper/graph_utils.py +283 -0
  22. tmapper_py-0.1.0/src/tmapper/knngraph.py +61 -0
  23. tmapper_py-0.1.0/src/tmapper/labeling.py +45 -0
  24. tmapper_py-0.1.0/src/tmapper/modularity.py +74 -0
  25. tmapper_py-0.1.0/src/tmapper/path_traffic.py +35 -0
  26. tmapper_py-0.1.0/src/tmapper/plotting.py +463 -0
  27. tmapper_py-0.1.0/src/tmapper/sample_data.py +19 -0
  28. tmapper_py-0.1.0/src/tmapper/sampledata/EL_temp.csv +57710 -0
  29. tmapper_py-0.1.0/src/tmapper/tcm_distance.py +63 -0
  30. tmapper_py-0.1.0/src/tmapper/tknndigraph.py +190 -0
  31. tmapper_py-0.1.0/src/tmapper_py.egg-info/PKG-INFO +108 -0
  32. tmapper_py-0.1.0/src/tmapper_py.egg-info/SOURCES.txt +44 -0
  33. tmapper_py-0.1.0/src/tmapper_py.egg-info/dependency_links.txt +1 -0
  34. tmapper_py-0.1.0/src/tmapper_py.egg-info/entry_points.txt +2 -0
  35. tmapper_py-0.1.0/src/tmapper_py.egg-info/requires.txt +23 -0
  36. tmapper_py-0.1.0/src/tmapper_py.egg-info/top_level.txt +1 -0
  37. tmapper_py-0.1.0/tests/test_app.py +940 -0
  38. tmapper_py-0.1.0/tests/test_core_pipeline.py +224 -0
  39. tmapper_py-0.1.0/tests/test_cycle_cluster.py +45 -0
  40. tmapper_py-0.1.0/tests/test_cycle_count.py +54 -0
  41. tmapper_py-0.1.0/tests/test_cycle_path_decomp.py +87 -0
  42. tmapper_py-0.1.0/tests/test_graph_utils.py +181 -0
  43. tmapper_py-0.1.0/tests/test_knngraph.py +68 -0
  44. tmapper_py-0.1.0/tests/test_modularity.py +61 -0
  45. tmapper_py-0.1.0/tests/test_path_traffic.py +21 -0
  46. tmapper_py-0.1.0/tests/test_visualization.py +187 -0
@@ -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,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,66 @@
1
+ # tmapper (Python)
2
+
3
+ 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.
4
+
5
+ **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.
6
+
7
+ For the full background, citation, and the original MATLAB implementation, see [tmapper2](https://github.com/Multiscale-Complex-Systems-Lab/tmapper2).
8
+
9
+ **Documentation, quickstart, and full API reference:** https://multiscale-complex-systems-lab.github.io/tmapper-py/
10
+
11
+ ## What's included
12
+
13
+ - **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)
14
+ - **Standalone graph builders**: `knngraph`, `cknngraph`
15
+ - **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`
16
+ - **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`
17
+ - **Interactive app**: `tmapper-app`, a browser front end for the whole pipeline -- see below
18
+
19
+ 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/`).
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ pip install tmapper-py # library
25
+ pip install "tmapper-py[app]" # library + the interactive app
26
+ ```
27
+
28
+ (The distribution is `tmapper-py` -- `tmapper` alone isn't available on PyPI,
29
+ see [Installation](https://multiscale-complex-systems-lab.github.io/tmapper-py/installation/).
30
+ Everything imports as `import tmapper` either way.)
31
+
32
+ For development, from a clone:
33
+
34
+ ```bash
35
+ git clone https://github.com/Multiscale-Complex-Systems-Lab/tmapper-py.git
36
+ cd tmapper-py
37
+ pip install -e ".[app,test]"
38
+ ```
39
+
40
+ ## Interactive app
41
+
42
+ 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:
43
+
44
+ ```bash
45
+ pip install "tmapper-py[app]"
46
+ tmapper-app
47
+ ```
48
+
49
+ 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.
50
+
51
+ See the [Interactive App guide](https://multiscale-complex-systems-lab.github.io/tmapper-py/app/) for a full walkthrough.
52
+
53
+ ## Running tests
54
+
55
+ ```bash
56
+ pytest
57
+ ```
58
+
59
+ ## Notes for users porting workflows from MATLAB
60
+
61
+ - Node labels are 0-indexed (Pythonic), unlike the MATLAB original's 1-indexed convention.
62
+ - 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.)
63
+
64
+ ## License
65
+
66
+ BSD 3-Clause License -- see [LICENSE](LICENSE).
@@ -0,0 +1,72 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ # Distribution name on PyPI, not the importable module (that's still
7
+ # `tmapper`, see [tool.setuptools.packages.find] below). Plain "tmapper" is
8
+ # blocked there by PyPI's anti-typosquatting check: it's a 1-character edit
9
+ # from the existing "mapper" package (a similarly-themed topological data
10
+ # analysis tool), which is exactly the pattern that check exists to catch.
11
+ name = "tmapper-py"
12
+ version = "0.1.0"
13
+ description = "Python port of Temporal Mapper 2 -- build attractor transition networks from time-series data"
14
+ readme = "README.md"
15
+ requires-python = ">=3.10"
16
+ # SPDX string + license-files: the `{ file = ... }` table form is
17
+ # deprecated and stops working on setuptools >= 77.
18
+ license = "BSD-3-Clause"
19
+ license-files = ["LICENSE"]
20
+ authors = [{ name = "Mengsen Zhang" }]
21
+ keywords = [
22
+ "temporal mapper", "topological data analysis", "mapper",
23
+ "dynamical systems", "time series", "attractor", "recurrence",
24
+ ]
25
+ classifiers = [
26
+ "Development Status :: 4 - Beta",
27
+ "Intended Audience :: Science/Research",
28
+ "Operating System :: OS Independent",
29
+ "Programming Language :: Python :: 3",
30
+ "Programming Language :: Python :: 3.10",
31
+ "Programming Language :: Python :: 3.11",
32
+ "Programming Language :: Python :: 3.12",
33
+ "Topic :: Scientific/Engineering :: Information Analysis",
34
+ "Topic :: Scientific/Engineering :: Visualization",
35
+ ]
36
+ dependencies = [
37
+ "numpy>=1.24",
38
+ "scipy>=1.10",
39
+ "networkx>=3.0",
40
+ ]
41
+
42
+ [project.optional-dependencies]
43
+ plot = ["matplotlib>=3.7", "python-igraph>=1.0", "pyvis>=0.3"]
44
+ # pandas is declared explicitly rather than leaned on as a streamlit
45
+ # transitive dependency: the app imports it directly, so it is a real
46
+ # requirement of ours. The core library stays pandas-free.
47
+ app = [
48
+ "streamlit>=1.30",
49
+ "pandas>=1.5",
50
+ "matplotlib>=3.7",
51
+ "python-igraph>=1.0",
52
+ "pyvis>=0.3",
53
+ ]
54
+ # the app tests drive the app, so they need its dependencies too
55
+ test = ["pytest>=7.0", "pandas>=1.5"]
56
+ docs = ["mkdocs-material>=9.5", "mkdocstrings[python]>=0.25"]
57
+
58
+ [project.scripts]
59
+ # `pip install "tmapper-py[app]"` then just `tmapper-app`
60
+ tmapper-app = "tmapper.app.launcher:main"
61
+
62
+ [project.urls]
63
+ Homepage = "https://github.com/Multiscale-Complex-Systems-Lab/tmapper-py"
64
+ "MATLAB original" = "https://github.com/Multiscale-Complex-Systems-Lab/tmapper2"
65
+
66
+ [tool.setuptools.packages.find]
67
+ where = ["src"]
68
+
69
+ [tool.setuptools.package-data]
70
+ # the bundled sample dataset, so the app's "Try sample data" button and the
71
+ # Quickstart work from a pip install and not only from a clone
72
+ tmapper = ["sampledata/*.csv"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,63 @@
1
+ from .sample_data import sample_data_path
2
+ from .tknndigraph import tknndigraph
3
+ from .filtergraph import filtergraph
4
+ from .labeling import find_node_label
5
+ from .tcm_distance import tcm_distance
6
+ from .plotting import plot_tmgraph, plot_tmgraph_tcm, plot_tmgraph_interactive
7
+ from .knngraph import knngraph
8
+ from .cknngraph import cknngraph
9
+ from .cycle_count import cycle_count
10
+ from .cycle_count2p import cycle_count2p, reorg_cycles
11
+ from .cycle_overlap import cycle_path_overlap
12
+ from .cycle_cluster import cycle_cluster
13
+ from .cycle_cluster_conn import cycle_cluster_conn
14
+ from .cycle_cutter import cycle_cutter
15
+ from .cycles_to_paths import cycles_to_paths
16
+ from .cycle_path_decomp import cycle_path_decomp
17
+ from .path_traffic import path_traffic
18
+ from .modularity import qasym, cal_mod
19
+ from .graph_utils import (
20
+ node_size,
21
+ node_measure,
22
+ normalize_geodesic,
23
+ normalize_tcm,
24
+ members_to_tidx,
25
+ subgraph_from_members,
26
+ sym_dyn_to_digraph,
27
+ digraph_to_graph,
28
+ find_blocks,
29
+ )
30
+
31
+ __all__ = [
32
+ "sample_data_path",
33
+ "tknndigraph",
34
+ "filtergraph",
35
+ "find_node_label",
36
+ "tcm_distance",
37
+ "plot_tmgraph",
38
+ "plot_tmgraph_tcm",
39
+ "plot_tmgraph_interactive",
40
+ "knngraph",
41
+ "cknngraph",
42
+ "cycle_count",
43
+ "cycle_count2p",
44
+ "reorg_cycles",
45
+ "cycle_path_overlap",
46
+ "cycle_cluster",
47
+ "cycle_cluster_conn",
48
+ "cycle_cutter",
49
+ "cycles_to_paths",
50
+ "cycle_path_decomp",
51
+ "path_traffic",
52
+ "qasym",
53
+ "cal_mod",
54
+ "node_size",
55
+ "node_measure",
56
+ "normalize_geodesic",
57
+ "normalize_tcm",
58
+ "members_to_tidx",
59
+ "subgraph_from_members",
60
+ "sym_dyn_to_digraph",
61
+ "digraph_to_graph",
62
+ "find_blocks",
63
+ ]
@@ -0,0 +1,22 @@
1
+ """Shared all-pairs shortest-path helper.
2
+
3
+ Uses scipy's compiled Dijkstra (via ``scipy.sparse.csgraph.shortest_path``)
4
+ instead of networkx's ``floyd_warshall_numpy``. The latter is O(n^3) with
5
+ substantial per-iteration numpy overhead and becomes the dominant cost at
6
+ realistic graph sizes (a few thousand nodes); our graphs are sparse (a
7
+ handful of edges per node from the k-NN construction), where Dijkstra from
8
+ every source is asymptotically and practically much faster.
9
+ """
10
+
11
+ import networkx as nx
12
+ from scipy.sparse.csgraph import shortest_path
13
+
14
+
15
+ def all_pairs_distance(g, nodelist, weight="weight"):
16
+ """All-pairs shortest-path distance matrix for ``g``, ordered by
17
+ ``nodelist``. ``weight=None`` treats every edge as weight 1 (hop
18
+ count); ``weight="weight"`` (default) uses ``g``'s edge weights.
19
+ Unreachable pairs are ``inf``, matching ``nx.floyd_warshall_numpy``.
20
+ """
21
+ adj = nx.to_scipy_sparse_array(g, nodelist=nodelist, weight=weight, dtype=float)
22
+ return shortest_path(adj, method="auto", directed=True)
@@ -0,0 +1,10 @@
1
+ """The interactive Streamlit app, and its launcher.
2
+
3
+ Lives inside the package so that ``pip install "tmapper[app]"`` ships it --
4
+ otherwise the app would only be runnable from a clone of the repository,
5
+ which is exactly the friction it exists to remove.
6
+ """
7
+
8
+ from .launcher import main
9
+
10
+ __all__ = ["main"]
@@ -0,0 +1,35 @@
1
+ """Console-script entry point: ``tmapper-app``.
2
+
3
+ A Streamlit app is a *script Streamlit runs*, not a program you import and
4
+ call, so this hands the app file to Streamlit's own CLI rather than
5
+ executing it directly -- running it as a plain script would just print
6
+ Streamlit's "missing ScriptRunContext" warnings and do nothing useful.
7
+
8
+ Any extra arguments are passed straight through, so the usual Streamlit
9
+ options still work::
10
+
11
+ tmapper-app --server.port 8600 --server.address 0.0.0.0
12
+ """
13
+
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ APP_FILE = Path(__file__).resolve().parent / "streamlit_app.py"
18
+
19
+
20
+ def main(argv=None):
21
+ try:
22
+ from streamlit.web import cli as stcli
23
+ except ImportError: # pragma: no cover - depends on install extras
24
+ raise SystemExit(
25
+ "The interactive app needs Streamlit, which is not installed.\n"
26
+ "Install it with: pip install 'tmapper[app]'"
27
+ )
28
+
29
+ args = sys.argv[1:] if argv is None else list(argv)
30
+ sys.argv = ["streamlit", "run", str(APP_FILE), *args]
31
+ return stcli.main()
32
+
33
+
34
+ if __name__ == "__main__": # pragma: no cover
35
+ sys.exit(main())