hons 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.
hons-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Russell J. Funk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
hons-0.1.0/MANIFEST.in ADDED
@@ -0,0 +1,6 @@
1
+ include examples/concept_network.py
2
+ recursive-include tests *.py
3
+
4
+ include examples/network_demo.py
5
+ include docs/network_demo.png
6
+ include docs/hons_logo.png
hons-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,112 @@
1
+ Metadata-Version: 2.4
2
+ Name: hons
3
+ Version: 0.1.0
4
+ Summary: Select network thresholds using persistent homology.
5
+ Author-email: "Russell J. Funk" <rfunk@umn.edu>
6
+ License-Expression: MIT
7
+ Project-URL: Source, https://github.com/rfunklab/hons
8
+ Project-URL: Issues, https://github.com/rfunklab/hons/issues
9
+ Keywords: persistent homology,network thresholding,topological data analysis
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: Topic :: Scientific/Engineering
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Requires-Python: <3.14,>=3.11
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: numpy<3,>=1.26.4
19
+ Requires-Dist: pandas<4,>=2.1.4
20
+ Requires-Dist: gudhi<3.12,>=3.10.1
21
+ Requires-Dist: networkx<4,>=3.2
22
+ Provides-Extra: plot
23
+ Requires-Dist: matplotlib<4,>=3.8; extra == "plot"
24
+ Dynamic: license-file
25
+
26
+ # Higher-order network selection
27
+
28
+ <img src="https://raw.githubusercontent.com/rfunklab/hons/a763c0b43e692ad65491f555a7ab080b6846567b/docs/hons_logo.png" alt="HONS logo" width="300">
29
+
30
+ Network thresholding helps researchers extract interpretable structure from dense relational data by removing nodes or edges according to their properties. Choosing the cutoffs is harder: a ground-truth network is rarely available, thresholds are often selected by trial and error, and small changes can produce substantially different networks. Criteria based on individual nodes or edges can also overlook connections that contribute to higher-order structure.
31
+
32
+ **HONS selects thresholds for numerical node attributes or edge weights using the network's cycles and cavities.** These features depend on how groups of connections fit together, beyond counts of nodes, edges or triangles. HONS uses persistent homology to measure those features across a grid of candidate thresholds and selects the network whose topological representation changes least under nearby threshold changes. Minimum feature-count constraints let researchers specify how many cycles and cavities candidate networks must contain.
33
+
34
+ ## Install
35
+
36
+ Use Python 3.11–3.13:
37
+
38
+ ```sh
39
+ pip install hons
40
+ ```
41
+
42
+ For plotting, run `pip install "hons[plot]"`.
43
+
44
+ ## NetworkX example
45
+
46
+ The main demo starts with a generated NetworkX graph containing three connected structures and extra connections. Each node has an assigned numerical `weight` to threshold, and each edge has an entry value called `filtration`. In an application, you would supply a measured node or edge attribute, such as frequency or contact duration:
47
+
48
+ ```python
49
+ import hons
50
+
51
+ network = hons.example_network()
52
+ selected = hons.threshold(
53
+ network,
54
+ node_attribute="weight",
55
+ lower=[0, 1, 2, 3, 4],
56
+ upper=[4, 5, 6, 7, 9],
57
+ filtration_range=(0, 1),
58
+ constraints=(50, 25),
59
+ )
60
+
61
+ print(network.number_of_nodes(), network.number_of_edges()) # 29 143
62
+ print(selected.number_of_nodes(), selected.number_of_edges()) # 18 41
63
+ ```
64
+
65
+ ![Input network and selected network](https://raw.githubusercontent.com/rfunklab/hons/a763c0b43e692ad65491f555a7ab080b6846567b/docs/network_demo.png)
66
+
67
+ Both objects are ordinary NetworkX graphs. The input remains unchanged, and the selected graph retains its node and edge attributes in their original units. Run `python examples/network_demo.py` from a source checkout to recreate the plot.
68
+
69
+ The example selects nodes with `weight` between **4 and 7**, inclusive. The selected network has five positive-lifetime H1 features and three H2 features across its filtration. H1 features describe cycles; H2 features describe cavities. `constraints=(50, 25)` requires their counts to reach at least the 50th and 25th percentiles, respectively, among selectable candidate networks.
70
+
71
+ ## Choose candidate thresholds
72
+
73
+ The `lower` and `upper` arrays define the search grid in the units of the attribute you want to threshold. The numbers above are choices for this small example. Choose bounds that cover the plausible cutoffs for your network; explicit lists, `numpy.linspace` and `numpy.geomspace` are all suitable ways to construct a grid. Grid range and spacing affect which settings are compared and which setting can be selected.
74
+
75
+ Supply increasing arrays with at least two lower bounds and one upper bound. HONS evaluates every lower/upper combination. The smallest lower bound supplies a neighboring value for the score and is excluded from selection. Bounds are inclusive. A lower bound above its paired upper bound removes all nodes in node-filtering mode or all edges in edge-filtering mode.
76
+
77
+ Use `node_attribute="name"` to select an induced subgraph, or `edge_attribute="name"` to filter edges while preserving all nodes. The input must be a simple, undirected NetworkX graph. HONS returns `None` when no candidate meets both feature-count constraints.
78
+
79
+ ## Specify the filtration
80
+
81
+ A filtration describes the order in which connections enter the topology calculation. Each edge needs a finite numerical entry value: a time, a distance or another quantity appropriate to your application. Smaller values enter earlier. All vertices are present at the start, and a clique enters when its last edge enters. The attribute used to remove nodes or edges and the entry values used for persistence have separate roles.
82
+
83
+ HONS accepts entry values in their original units and converts them internally to 0–1 for the persistence images. By default, the conversion uses the minimum and maximum edge values in the full input graph. Every candidate uses that same range. Set `filtration_range=(start, stop)` when you have a known observation window or want to use a fixed range across multiple input graphs. For example, use `filtration="first_seen", filtration_range=(1920, 2021)` for edges dated by first appearance. The demo explicitly uses its generating interval, `(0, 1)`.
84
+
85
+ The fixed image grid and Gaussian width apply to this normalized scale. A change of units, with the range changed accordingly, preserves the calculation. A different definition of entry order can change the result. For a strength-based filtration in which stronger edges should enter earlier, use negative strength as the entry value. A zero-width normalization range maps entry values to zero. An empty input graph defaults to a 0–1 range.
86
+
87
+ ## Example application: scientific concepts
88
+
89
+ The paper applies HONS to concepts that co-occur in scientific articles. In this application, nodes represent concepts, document frequency supplies the attribute to threshold, and an edge's first co-occurrence year supplies its entry value. `examples/concept_network.py` shows how to prepare generated article–concept records as an ordinary NetworkX graph and pass that graph to HONS. This example uses a fixed observation window across candidates. The paper's empirical analysis dates each retained network from its earliest retained concept; the replication repository preserves that preparation in the saved diagrams.
90
+
91
+ | Example | Purpose | Command from a source checkout |
92
+ | --- | --- | --- |
93
+ | `network_demo.py` | Select from an existing graph and draw the before/after comparison. | `python examples/network_demo.py` |
94
+ | `concept_network.py` | Prepare article–concept records for the same general API. | `python examples/concept_network.py` |
95
+
96
+ The separate [hons-replication](https://github.com/rfunklab/hons-replication) repository reproduces calculations from the paper's saved persistence diagrams and demonstrates edge-weight thresholding on an open workplace contact network.
97
+
98
+ ## Inspect the calculation
99
+
100
+ Set `return_details=True` to return `(selected, details)`. `details["grid"]` contains thresholds, network sizes, feature counts and scores; `details["filtration_range"]` records the range used for normalization. The dictionary also includes normalized persistence diagrams, image vectors and near-ties. The selected graph's `graph["hons"]` attribute records its bounds, score and feature counts.
101
+
102
+ The calculation uses H1 and H2 persistence images, a 20 × 20 sampling grid per dimension, Gaussian standard deviation 0.1, lifetime weights and coefficients in Z/11Z. Essential features are included in selection; infinite deaths become 1.00001 for imaging. Neighboring image distances are divided by their parameter separations, averaged in each direction and combined by the Euclidean norm. Dense graphs can be expensive because computing H2 requires clique expansion through tetrahedra.
103
+
104
+ For an existing grid of persistence diagrams, use `rho`, `objective` and `select` directly. Diagrams are pandas DataFrames with `dimension`, `birth` and `death` columns and must use a common normalized scale. Dictionary keys are `(upper_index, lower_index)`. The `persistence` helper accepts node identifiers and an edge-to-entry-value mapping; supply common `start` and `stop` bounds when comparing multiple networks. Exact minima follow sorted cell order, with near-ties reported within an absolute score tolerance of 1e-12.
105
+
106
+ Run the tests from a source checkout with `python -m unittest discover -s tests`.
107
+
108
+ ## Paper and license
109
+
110
+ Adam Schroeder, Russell Funk, Jingyi Guan, Taylor Okonek and Lori Ziegelmeier. *Higher-Order Network Structure Inference: A Topological Approach to Network Selection*.
111
+
112
+ The MIT [license](LICENSE) applies to this repository's code, documentation and original assets. Contact Russell Funk at rfunk@umn.edu.
hons-0.1.0/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # Higher-order network selection
2
+
3
+ <img src="https://raw.githubusercontent.com/rfunklab/hons/a763c0b43e692ad65491f555a7ab080b6846567b/docs/hons_logo.png" alt="HONS logo" width="300">
4
+
5
+ Network thresholding helps researchers extract interpretable structure from dense relational data by removing nodes or edges according to their properties. Choosing the cutoffs is harder: a ground-truth network is rarely available, thresholds are often selected by trial and error, and small changes can produce substantially different networks. Criteria based on individual nodes or edges can also overlook connections that contribute to higher-order structure.
6
+
7
+ **HONS selects thresholds for numerical node attributes or edge weights using the network's cycles and cavities.** These features depend on how groups of connections fit together, beyond counts of nodes, edges or triangles. HONS uses persistent homology to measure those features across a grid of candidate thresholds and selects the network whose topological representation changes least under nearby threshold changes. Minimum feature-count constraints let researchers specify how many cycles and cavities candidate networks must contain.
8
+
9
+ ## Install
10
+
11
+ Use Python 3.11–3.13:
12
+
13
+ ```sh
14
+ pip install hons
15
+ ```
16
+
17
+ For plotting, run `pip install "hons[plot]"`.
18
+
19
+ ## NetworkX example
20
+
21
+ The main demo starts with a generated NetworkX graph containing three connected structures and extra connections. Each node has an assigned numerical `weight` to threshold, and each edge has an entry value called `filtration`. In an application, you would supply a measured node or edge attribute, such as frequency or contact duration:
22
+
23
+ ```python
24
+ import hons
25
+
26
+ network = hons.example_network()
27
+ selected = hons.threshold(
28
+ network,
29
+ node_attribute="weight",
30
+ lower=[0, 1, 2, 3, 4],
31
+ upper=[4, 5, 6, 7, 9],
32
+ filtration_range=(0, 1),
33
+ constraints=(50, 25),
34
+ )
35
+
36
+ print(network.number_of_nodes(), network.number_of_edges()) # 29 143
37
+ print(selected.number_of_nodes(), selected.number_of_edges()) # 18 41
38
+ ```
39
+
40
+ ![Input network and selected network](https://raw.githubusercontent.com/rfunklab/hons/a763c0b43e692ad65491f555a7ab080b6846567b/docs/network_demo.png)
41
+
42
+ Both objects are ordinary NetworkX graphs. The input remains unchanged, and the selected graph retains its node and edge attributes in their original units. Run `python examples/network_demo.py` from a source checkout to recreate the plot.
43
+
44
+ The example selects nodes with `weight` between **4 and 7**, inclusive. The selected network has five positive-lifetime H1 features and three H2 features across its filtration. H1 features describe cycles; H2 features describe cavities. `constraints=(50, 25)` requires their counts to reach at least the 50th and 25th percentiles, respectively, among selectable candidate networks.
45
+
46
+ ## Choose candidate thresholds
47
+
48
+ The `lower` and `upper` arrays define the search grid in the units of the attribute you want to threshold. The numbers above are choices for this small example. Choose bounds that cover the plausible cutoffs for your network; explicit lists, `numpy.linspace` and `numpy.geomspace` are all suitable ways to construct a grid. Grid range and spacing affect which settings are compared and which setting can be selected.
49
+
50
+ Supply increasing arrays with at least two lower bounds and one upper bound. HONS evaluates every lower/upper combination. The smallest lower bound supplies a neighboring value for the score and is excluded from selection. Bounds are inclusive. A lower bound above its paired upper bound removes all nodes in node-filtering mode or all edges in edge-filtering mode.
51
+
52
+ Use `node_attribute="name"` to select an induced subgraph, or `edge_attribute="name"` to filter edges while preserving all nodes. The input must be a simple, undirected NetworkX graph. HONS returns `None` when no candidate meets both feature-count constraints.
53
+
54
+ ## Specify the filtration
55
+
56
+ A filtration describes the order in which connections enter the topology calculation. Each edge needs a finite numerical entry value: a time, a distance or another quantity appropriate to your application. Smaller values enter earlier. All vertices are present at the start, and a clique enters when its last edge enters. The attribute used to remove nodes or edges and the entry values used for persistence have separate roles.
57
+
58
+ HONS accepts entry values in their original units and converts them internally to 0–1 for the persistence images. By default, the conversion uses the minimum and maximum edge values in the full input graph. Every candidate uses that same range. Set `filtration_range=(start, stop)` when you have a known observation window or want to use a fixed range across multiple input graphs. For example, use `filtration="first_seen", filtration_range=(1920, 2021)` for edges dated by first appearance. The demo explicitly uses its generating interval, `(0, 1)`.
59
+
60
+ The fixed image grid and Gaussian width apply to this normalized scale. A change of units, with the range changed accordingly, preserves the calculation. A different definition of entry order can change the result. For a strength-based filtration in which stronger edges should enter earlier, use negative strength as the entry value. A zero-width normalization range maps entry values to zero. An empty input graph defaults to a 0–1 range.
61
+
62
+ ## Example application: scientific concepts
63
+
64
+ The paper applies HONS to concepts that co-occur in scientific articles. In this application, nodes represent concepts, document frequency supplies the attribute to threshold, and an edge's first co-occurrence year supplies its entry value. `examples/concept_network.py` shows how to prepare generated article–concept records as an ordinary NetworkX graph and pass that graph to HONS. This example uses a fixed observation window across candidates. The paper's empirical analysis dates each retained network from its earliest retained concept; the replication repository preserves that preparation in the saved diagrams.
65
+
66
+ | Example | Purpose | Command from a source checkout |
67
+ | --- | --- | --- |
68
+ | `network_demo.py` | Select from an existing graph and draw the before/after comparison. | `python examples/network_demo.py` |
69
+ | `concept_network.py` | Prepare article–concept records for the same general API. | `python examples/concept_network.py` |
70
+
71
+ The separate [hons-replication](https://github.com/rfunklab/hons-replication) repository reproduces calculations from the paper's saved persistence diagrams and demonstrates edge-weight thresholding on an open workplace contact network.
72
+
73
+ ## Inspect the calculation
74
+
75
+ Set `return_details=True` to return `(selected, details)`. `details["grid"]` contains thresholds, network sizes, feature counts and scores; `details["filtration_range"]` records the range used for normalization. The dictionary also includes normalized persistence diagrams, image vectors and near-ties. The selected graph's `graph["hons"]` attribute records its bounds, score and feature counts.
76
+
77
+ The calculation uses H1 and H2 persistence images, a 20 × 20 sampling grid per dimension, Gaussian standard deviation 0.1, lifetime weights and coefficients in Z/11Z. Essential features are included in selection; infinite deaths become 1.00001 for imaging. Neighboring image distances are divided by their parameter separations, averaged in each direction and combined by the Euclidean norm. Dense graphs can be expensive because computing H2 requires clique expansion through tetrahedra.
78
+
79
+ For an existing grid of persistence diagrams, use `rho`, `objective` and `select` directly. Diagrams are pandas DataFrames with `dimension`, `birth` and `death` columns and must use a common normalized scale. Dictionary keys are `(upper_index, lower_index)`. The `persistence` helper accepts node identifiers and an edge-to-entry-value mapping; supply common `start` and `stop` bounds when comparing multiple networks. Exact minima follow sorted cell order, with near-ties reported within an absolute score tolerance of 1e-12.
80
+
81
+ Run the tests from a source checkout with `python -m unittest discover -s tests`.
82
+
83
+ ## Paper and license
84
+
85
+ Adam Schroeder, Russell Funk, Jingyi Guan, Taylor Okonek and Lori Ziegelmeier. *Higher-Order Network Structure Inference: A Topological Approach to Network Selection*.
86
+
87
+ The MIT [license](LICENSE) applies to this repository's code, documentation and original assets. Contact Russell Funk at rfunk@umn.edu.
Binary file
Binary file
@@ -0,0 +1,76 @@
1
+ """Prepare article/concept records for the general NetworkX thresholding API."""
2
+ from itertools import combinations, product
3
+ from pathlib import Path
4
+
5
+ import networkx as nx
6
+ import numpy as np
7
+ import pandas as pd
8
+
9
+ import hons
10
+
11
+
12
+ def to_network(records):
13
+ """Count articles per concept and date edges by first co-occurrence."""
14
+ if records.groupby("article_id").year.nunique().ne(1).any():
15
+ raise ValueError("Each article must have one publication year.")
16
+ records = records.drop_duplicates(["article_id", "concept"])
17
+ counts = records.groupby("concept").article_id.nunique()
18
+ graph = nx.Graph()
19
+ graph.add_nodes_from((concept, {"frequency": int(count)}) for concept, count in counts.items())
20
+ for _, group in records.groupby("article_id", sort=False):
21
+ year = float(group.year.iloc[0])
22
+ for a, b in combinations(group.concept, 2):
23
+ previous = graph.get_edge_data(a, b, {}).get("first_seen", year)
24
+ graph.add_edge(a, b, first_seen=min(year, previous))
25
+ return graph
26
+
27
+
28
+ def example_data():
29
+ """Create an octahedral network, a square, and a frequent connecting concept."""
30
+ articles = []
31
+ groups = [("x0", "x1"), ("y0", "y1"), ("z0", "z1")]
32
+ for group_a, group_b in combinations(groups, 2):
33
+ for a, b in product(group_a, group_b):
34
+ articles.append((2000 + len(articles) % 4, [a, b]))
35
+ articles.append((2008, ["x0", "x1"]))
36
+ for concept in ("x0", "x1", "y0", "y1", "z0", "z1"):
37
+ articles.append((2010, ["hub", concept]))
38
+ for year, pair in [(2000, ["s0", "s1"]), (2001, ["s1", "s2"]),
39
+ (2002, ["s2", "s3"]), (2003, ["s3", "s0"]),
40
+ (2008, ["s0", "s2"])]:
41
+ articles.append((year, pair))
42
+
43
+ # singleton articles vary document frequencies without introducing edges
44
+ frequencies = {"x0": 8, "x1": 8, "y0": 6, "y1": 6, "z0": 5,
45
+ "z1": 5, "hub": 11, "s0": 4, "s1": 3, "s2": 4, "s3": 3}
46
+ for concept, frequency in frequencies.items():
47
+ current = sum(concept in concepts for _, concepts in articles)
48
+ for _ in range(frequency - current):
49
+ articles.append((2000, [concept]))
50
+ return pd.DataFrame(
51
+ [(f"a{i:03d}", year, concept)
52
+ for i, (year, concepts) in enumerate(articles) for concept in concepts],
53
+ columns=["article_id", "year", "concept"]
54
+ )
55
+
56
+
57
+ def main():
58
+ records = example_data()
59
+ network = to_network(records)
60
+ selected, details = hons.threshold(
61
+ network, node_attribute="frequency", filtration="first_seen",
62
+ filtration_range=(records.year.min(), records.year.max()),
63
+ lower=[1, 3, 5, 7], upper=[7, 9, 12],
64
+ constraints=(50, 50), return_details=True,
65
+ )
66
+ output = Path(__file__).resolve().parent / "output" / "concept_network"
67
+ output.mkdir(parents=True, exist_ok=True)
68
+ records.to_csv(output / "articles.csv", index=False)
69
+ details["grid"].to_csv(output / "grid.csv", index=False)
70
+ print(f"{records.article_id.nunique()} articles; {len(network)} concepts; "
71
+ f"{len(details['grid'])} candidate networks.")
72
+ print(selected.graph["hons"] if selected is not None else "No feasible selection.")
73
+
74
+
75
+ if __name__ == "__main__":
76
+ main()
@@ -0,0 +1,29 @@
1
+ """Select a NetworkX graph and draw the input and output with shared positions."""
2
+ from pathlib import Path
3
+
4
+ import matplotlib.pyplot as plt
5
+ import networkx as nx
6
+
7
+ import hons
8
+
9
+ network = hons.example_network()
10
+ selected = hons.threshold(network, node_attribute="weight",
11
+ lower=[0, 1, 2, 3, 4], upper=[4, 5, 6, 7, 9],
12
+ filtration_range=(0, 1), constraints=(50, 25))
13
+ positions = nx.spring_layout(network, seed=7)
14
+ fig, axes = plt.subplots(1, 2, figsize=(10, 4.5), layout="constrained")
15
+ for ax, graph, title in zip(axes, [network, selected], ["Input network", "Selected network"]):
16
+ nx.draw_networkx_edges(graph, positions, ax=ax, edge_color="#b5c2c8", width=.7)
17
+ nx.draw_networkx_nodes(graph, positions, ax=ax, node_size=95,
18
+ node_color="#f07967" if graph is selected else "#24495b",
19
+ edgecolors="#173645", linewidths=.6)
20
+ ax.set_title(f"{title}\n{len(graph)} nodes, {graph.number_of_edges()} edges")
21
+ ax.set_xlim(-1.2, 1.2)
22
+ ax.set_ylim(-1.2, 1.2)
23
+ ax.axis("off")
24
+ output = Path(__file__).resolve().parents[1] / "docs"
25
+ output.mkdir(exist_ok=True)
26
+ fig.savefig(output / "network_demo.png", dpi=180)
27
+ plt.close(fig)
28
+ print(selected.graph["hons"])
29
+ print(f"Saved {output / 'network_demo.png'}")
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77.0.3"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "hons"
7
+ version = "0.1.0"
8
+ description = "Select network thresholds using persistent homology."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11,<3.14"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{name = "Russell J. Funk", email = "rfunk@umn.edu"}]
14
+ keywords = ["persistent homology", "network thresholding", "topological data analysis"]
15
+ classifiers = ["Intended Audience :: Science/Research", "Topic :: Scientific/Engineering", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13"]
16
+ dependencies = ["numpy>=1.26.4,<3", "pandas>=2.1.4,<4", "gudhi>=3.10.1,<3.12", "networkx>=3.2,<4"]
17
+
18
+ [project.optional-dependencies]
19
+ plot = ["matplotlib>=3.8,<4"]
20
+
21
+ [project.urls]
22
+ Source = "https://github.com/rfunklab/hons"
23
+ Issues = "https://github.com/rfunklab/hons/issues"
24
+
25
+ [tool.setuptools]
26
+ py-modules = ["hons"]
27
+ package-dir = {"" = "src"}
hons-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,112 @@
1
+ Metadata-Version: 2.4
2
+ Name: hons
3
+ Version: 0.1.0
4
+ Summary: Select network thresholds using persistent homology.
5
+ Author-email: "Russell J. Funk" <rfunk@umn.edu>
6
+ License-Expression: MIT
7
+ Project-URL: Source, https://github.com/rfunklab/hons
8
+ Project-URL: Issues, https://github.com/rfunklab/hons/issues
9
+ Keywords: persistent homology,network thresholding,topological data analysis
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: Topic :: Scientific/Engineering
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Requires-Python: <3.14,>=3.11
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: numpy<3,>=1.26.4
19
+ Requires-Dist: pandas<4,>=2.1.4
20
+ Requires-Dist: gudhi<3.12,>=3.10.1
21
+ Requires-Dist: networkx<4,>=3.2
22
+ Provides-Extra: plot
23
+ Requires-Dist: matplotlib<4,>=3.8; extra == "plot"
24
+ Dynamic: license-file
25
+
26
+ # Higher-order network selection
27
+
28
+ <img src="https://raw.githubusercontent.com/rfunklab/hons/a763c0b43e692ad65491f555a7ab080b6846567b/docs/hons_logo.png" alt="HONS logo" width="300">
29
+
30
+ Network thresholding helps researchers extract interpretable structure from dense relational data by removing nodes or edges according to their properties. Choosing the cutoffs is harder: a ground-truth network is rarely available, thresholds are often selected by trial and error, and small changes can produce substantially different networks. Criteria based on individual nodes or edges can also overlook connections that contribute to higher-order structure.
31
+
32
+ **HONS selects thresholds for numerical node attributes or edge weights using the network's cycles and cavities.** These features depend on how groups of connections fit together, beyond counts of nodes, edges or triangles. HONS uses persistent homology to measure those features across a grid of candidate thresholds and selects the network whose topological representation changes least under nearby threshold changes. Minimum feature-count constraints let researchers specify how many cycles and cavities candidate networks must contain.
33
+
34
+ ## Install
35
+
36
+ Use Python 3.11–3.13:
37
+
38
+ ```sh
39
+ pip install hons
40
+ ```
41
+
42
+ For plotting, run `pip install "hons[plot]"`.
43
+
44
+ ## NetworkX example
45
+
46
+ The main demo starts with a generated NetworkX graph containing three connected structures and extra connections. Each node has an assigned numerical `weight` to threshold, and each edge has an entry value called `filtration`. In an application, you would supply a measured node or edge attribute, such as frequency or contact duration:
47
+
48
+ ```python
49
+ import hons
50
+
51
+ network = hons.example_network()
52
+ selected = hons.threshold(
53
+ network,
54
+ node_attribute="weight",
55
+ lower=[0, 1, 2, 3, 4],
56
+ upper=[4, 5, 6, 7, 9],
57
+ filtration_range=(0, 1),
58
+ constraints=(50, 25),
59
+ )
60
+
61
+ print(network.number_of_nodes(), network.number_of_edges()) # 29 143
62
+ print(selected.number_of_nodes(), selected.number_of_edges()) # 18 41
63
+ ```
64
+
65
+ ![Input network and selected network](https://raw.githubusercontent.com/rfunklab/hons/a763c0b43e692ad65491f555a7ab080b6846567b/docs/network_demo.png)
66
+
67
+ Both objects are ordinary NetworkX graphs. The input remains unchanged, and the selected graph retains its node and edge attributes in their original units. Run `python examples/network_demo.py` from a source checkout to recreate the plot.
68
+
69
+ The example selects nodes with `weight` between **4 and 7**, inclusive. The selected network has five positive-lifetime H1 features and three H2 features across its filtration. H1 features describe cycles; H2 features describe cavities. `constraints=(50, 25)` requires their counts to reach at least the 50th and 25th percentiles, respectively, among selectable candidate networks.
70
+
71
+ ## Choose candidate thresholds
72
+
73
+ The `lower` and `upper` arrays define the search grid in the units of the attribute you want to threshold. The numbers above are choices for this small example. Choose bounds that cover the plausible cutoffs for your network; explicit lists, `numpy.linspace` and `numpy.geomspace` are all suitable ways to construct a grid. Grid range and spacing affect which settings are compared and which setting can be selected.
74
+
75
+ Supply increasing arrays with at least two lower bounds and one upper bound. HONS evaluates every lower/upper combination. The smallest lower bound supplies a neighboring value for the score and is excluded from selection. Bounds are inclusive. A lower bound above its paired upper bound removes all nodes in node-filtering mode or all edges in edge-filtering mode.
76
+
77
+ Use `node_attribute="name"` to select an induced subgraph, or `edge_attribute="name"` to filter edges while preserving all nodes. The input must be a simple, undirected NetworkX graph. HONS returns `None` when no candidate meets both feature-count constraints.
78
+
79
+ ## Specify the filtration
80
+
81
+ A filtration describes the order in which connections enter the topology calculation. Each edge needs a finite numerical entry value: a time, a distance or another quantity appropriate to your application. Smaller values enter earlier. All vertices are present at the start, and a clique enters when its last edge enters. The attribute used to remove nodes or edges and the entry values used for persistence have separate roles.
82
+
83
+ HONS accepts entry values in their original units and converts them internally to 0–1 for the persistence images. By default, the conversion uses the minimum and maximum edge values in the full input graph. Every candidate uses that same range. Set `filtration_range=(start, stop)` when you have a known observation window or want to use a fixed range across multiple input graphs. For example, use `filtration="first_seen", filtration_range=(1920, 2021)` for edges dated by first appearance. The demo explicitly uses its generating interval, `(0, 1)`.
84
+
85
+ The fixed image grid and Gaussian width apply to this normalized scale. A change of units, with the range changed accordingly, preserves the calculation. A different definition of entry order can change the result. For a strength-based filtration in which stronger edges should enter earlier, use negative strength as the entry value. A zero-width normalization range maps entry values to zero. An empty input graph defaults to a 0–1 range.
86
+
87
+ ## Example application: scientific concepts
88
+
89
+ The paper applies HONS to concepts that co-occur in scientific articles. In this application, nodes represent concepts, document frequency supplies the attribute to threshold, and an edge's first co-occurrence year supplies its entry value. `examples/concept_network.py` shows how to prepare generated article–concept records as an ordinary NetworkX graph and pass that graph to HONS. This example uses a fixed observation window across candidates. The paper's empirical analysis dates each retained network from its earliest retained concept; the replication repository preserves that preparation in the saved diagrams.
90
+
91
+ | Example | Purpose | Command from a source checkout |
92
+ | --- | --- | --- |
93
+ | `network_demo.py` | Select from an existing graph and draw the before/after comparison. | `python examples/network_demo.py` |
94
+ | `concept_network.py` | Prepare article–concept records for the same general API. | `python examples/concept_network.py` |
95
+
96
+ The separate [hons-replication](https://github.com/rfunklab/hons-replication) repository reproduces calculations from the paper's saved persistence diagrams and demonstrates edge-weight thresholding on an open workplace contact network.
97
+
98
+ ## Inspect the calculation
99
+
100
+ Set `return_details=True` to return `(selected, details)`. `details["grid"]` contains thresholds, network sizes, feature counts and scores; `details["filtration_range"]` records the range used for normalization. The dictionary also includes normalized persistence diagrams, image vectors and near-ties. The selected graph's `graph["hons"]` attribute records its bounds, score and feature counts.
101
+
102
+ The calculation uses H1 and H2 persistence images, a 20 × 20 sampling grid per dimension, Gaussian standard deviation 0.1, lifetime weights and coefficients in Z/11Z. Essential features are included in selection; infinite deaths become 1.00001 for imaging. Neighboring image distances are divided by their parameter separations, averaged in each direction and combined by the Euclidean norm. Dense graphs can be expensive because computing H2 requires clique expansion through tetrahedra.
103
+
104
+ For an existing grid of persistence diagrams, use `rho`, `objective` and `select` directly. Diagrams are pandas DataFrames with `dimension`, `birth` and `death` columns and must use a common normalized scale. Dictionary keys are `(upper_index, lower_index)`. The `persistence` helper accepts node identifiers and an edge-to-entry-value mapping; supply common `start` and `stop` bounds when comparing multiple networks. Exact minima follow sorted cell order, with near-ties reported within an absolute score tolerance of 1e-12.
105
+
106
+ Run the tests from a source checkout with `python -m unittest discover -s tests`.
107
+
108
+ ## Paper and license
109
+
110
+ Adam Schroeder, Russell Funk, Jingyi Guan, Taylor Okonek and Lori Ziegelmeier. *Higher-Order Network Structure Inference: A Topological Approach to Network Selection*.
111
+
112
+ The MIT [license](LICENSE) applies to this repository's code, documentation and original assets. Contact Russell Funk at rfunk@umn.edu.
@@ -0,0 +1,19 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ pyproject.toml
5
+ docs/hons_logo.png
6
+ docs/network_demo.png
7
+ examples/concept_network.py
8
+ examples/network_demo.py
9
+ src/hons.py
10
+ src/hons.egg-info/PKG-INFO
11
+ src/hons.egg-info/SOURCES.txt
12
+ src/hons.egg-info/dependency_links.txt
13
+ src/hons.egg-info/requires.txt
14
+ src/hons.egg-info/top_level.txt
15
+ tests/test_concept_example.py
16
+ tests/test_filtration.py
17
+ tests/test_method.py
18
+ tests/test_network_api.py
19
+ tests/test_properties.py
@@ -0,0 +1,7 @@
1
+ numpy<3,>=1.26.4
2
+ pandas<4,>=2.1.4
3
+ gudhi<3.12,>=3.10.1
4
+ networkx<4,>=3.2
5
+
6
+ [plot]
7
+ matplotlib<4,>=3.8
@@ -0,0 +1 @@
1
+ hons