directedstructure 0.2.3__tar.gz → 0.3.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.
- {directedstructure-0.2.3 → directedstructure-0.3.0}/PKG-INFO +48 -14
- {directedstructure-0.2.3 → directedstructure-0.3.0}/README.md +47 -13
- {directedstructure-0.2.3 → directedstructure-0.3.0}/bindings/python/bindings.cpp +196 -30
- directedstructure-0.3.0/examples/python/adjacency.png +0 -0
- directedstructure-0.3.0/examples/python/example.py +69 -0
- directedstructure-0.3.0/examples/python/node_properties.png +0 -0
- directedstructure-0.3.0/examples/python/q_trajectory.png +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/python/directedstructure/src/directedstructure/__init__.py +2 -1
- {directedstructure-0.2.3 → directedstructure-0.3.0}/python/directedstructure/src/directedstructure/_version.py +3 -3
- {directedstructure-0.2.3 → directedstructure-0.3.0}/python/directedstructure/src/directedstructure/core.py +274 -34
- {directedstructure-0.2.3 → directedstructure-0.3.0}/python/directedstructure/src/directedstructure/input_output.py +302 -44
- {directedstructure-0.2.3 → directedstructure-0.3.0}/src/MCMC_bookkeeping.cpp +83 -17
- {directedstructure-0.2.3 → directedstructure-0.3.0}/src/MCMC_core.cpp +47 -3
- {directedstructure-0.2.3 → directedstructure-0.3.0}/src/MCMC_core.h +18 -5
- {directedstructure-0.2.3 → directedstructure-0.3.0}/src/MCMC_proposals.cpp +200 -19
- {directedstructure-0.2.3 → directedstructure-0.3.0}/src/fit.cpp +19 -3
- {directedstructure-0.2.3 → directedstructure-0.3.0}/src/fit.h +7 -3
- directedstructure-0.3.0/src/fit_result.cpp +304 -0
- directedstructure-0.3.0/src/fit_result.h +80 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/src/globals.h +1 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/src/helpers.cpp +4 -0
- directedstructure-0.3.0/tests/python/test_hw_routing.py +101 -0
- directedstructure-0.3.0/tests/python/test_hw_sensitivity.py +117 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/tests/python/test_package.py +325 -41
- directedstructure-0.3.0/tests/python/test_weighted_sbm_model_selection.py +234 -0
- directedstructure-0.2.3/examples/data/generate_synthetic_networks.py +0 -139
- directedstructure-0.2.3/examples/data/networks/example_3_nodes.gml +0 -103
- directedstructure-0.2.3/examples/data/networks/friends_raw.gml +0 -1079
- directedstructure-0.2.3/examples/data/networks/highschool_30_raw.gml +0 -12183
- directedstructure-0.2.3/examples/data/networks/highschool_51_raw.gml +0 -6307
- directedstructure-0.2.3/examples/data/networks/highschool_84_raw.gml +0 -15219
- directedstructure-0.2.3/examples/data/networks/snr025_sigC2_rep07.gml +0 -2951
- directedstructure-0.2.3/examples/data/networks/snr150_sigC5_rep09.gml +0 -3003
- directedstructure-0.2.3/examples/data/process_friendship_nominations.py +0 -42
- directedstructure-0.2.3/examples/data/take_samples.py +0 -19
- directedstructure-0.2.3/examples/data/test_2.gml +0 -21
- directedstructure-0.2.3/examples/directed_undirected_communities.py +0 -76
- directedstructure-0.2.3/examples/highschool_30_group_assignments_comparison.png +0 -0
- directedstructure-0.2.3/examples/highschool_30_neutral_inference.gml +0 -7461
- directedstructure-0.2.3/examples/highschool_30_original_inference.gml +0 -13306
- directedstructure-0.2.3/examples/highschool_51_group_assignments_comparison.png +0 -0
- directedstructure-0.2.3/examples/highschool_51_neutral_inference.gml +0 -4660
- directedstructure-0.2.3/examples/highschool_51_original_inference.gml +0 -7490
- directedstructure-0.2.3/examples/network_properties.csv +0 -11
- directedstructure-0.2.3/examples/node_properties.csv +0 -493
- directedstructure-0.2.3/examples/prediction.py +0 -1
- directedstructure-0.2.3/examples/python/consensus.py +0 -27
- directedstructure-0.2.3/examples/python/directed_undirected_communities.py +0 -76
- directedstructure-0.2.3/examples/python/prediction.py +0 -1
- directedstructure-0.2.3/examples/python/sampling.py +0 -1
- directedstructure-0.2.3/examples/python/speed_test.py +0 -1
- directedstructure-0.2.3/examples/python/testing.py +0 -110
- directedstructure-0.2.3/examples/python/testing_fixed.py +0 -104
- directedstructure-0.2.3/examples/sampling.py +0 -1
- directedstructure-0.2.3/examples/speed_test.py +0 -1
- directedstructure-0.2.3/examples/testing copy.png +0 -0
- directedstructure-0.2.3/examples/testing.png +0 -0
- directedstructure-0.2.3/examples/testing.py +0 -242
- directedstructure-0.2.3/examples/testing_coincidence_comparison.png +0 -0
- directedstructure-0.2.3/examples/testing_fixed.py +0 -104
- directedstructure-0.2.3/examples/testing_graph.png +0 -0
- directedstructure-0.2.3/examples/testing_inferences.gml +0 -3621
- directedstructure-0.2.3/examples/testing_node_properties.png +0 -0
- directedstructure-0.2.3/examples/testing_node_properties_from_samples.png +0 -0
- directedstructure-0.2.3/examples/testing_num_groups.png +0 -0
- directedstructure-0.2.3/examples/testing_samples.csv +0 -1001
- directedstructure-0.2.3/src/fit_result.cpp +0 -113
- directedstructure-0.2.3/src/fit_result.h +0 -48
- {directedstructure-0.2.3 → directedstructure-0.3.0}/.copier-answers.yml +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/.git_archival.txt +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/.gitattributes +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/.github/CONTRIBUTING.md +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/.github/dependabot.yml +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/.github/release.yml +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/.github/workflows/release.yml +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/.gitignore +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/.pre-commit-config.yaml +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/.readthedocs.yaml +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/CMakeLists.txt +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/LICENSE +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/Makefile +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/TODO.md +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/docs/conf.py +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/docs/examples/prediction.md +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/docs/examples/speed_test.md +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/docs/examples/visualization.md +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/docs/index.md +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/docs/modules.md +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/examples/data/networks/business_depts.gml +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/examples/data/networks/championship_2021.gml +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/examples/data/networks/english-football-2025.gml +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/examples/data/networks/friends.gml +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/examples/data/networks/highschool_30.gml +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/examples/data/networks/highschool_51.gml +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/examples/data/networks/highschool_84.gml +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/node_properties.csv +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/noxfile.py +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/pyproject.toml +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/python/directedstructure/src/directedstructure/MCMC_core.pyi +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/python/directedstructure/src/directedstructure/_core.pyi +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/python/directedstructure/src/directedstructure/_graph_cache.py +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/python/directedstructure/src/directedstructure/_util.py +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/python/directedstructure/src/directedstructure/py.typed +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/src/globals.cpp +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/src/helpers.h +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/tests/README.md +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/tests/data/inputs/wins_ties_3node_point_eval.csv +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/tests/data/outputs/triangle_coupled_coincidence.csv +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/tests/data/outputs/triangle_independent_coincidence.csv +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/tests/data/outputs/wins_ties_3node_coincidence.csv +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/tests/data/outputs/wins_ties_3node_point_eval_components.csv +0 -0
- {directedstructure-0.2.3 → directedstructure-0.3.0}/tests/mcmc_core_test.cpp +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: directedstructure
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Infer communities, hierarchies, and their connection in directed graphs
|
|
5
5
|
Author-Email: Maximilian Jerdee <mjerdee@santafe.edu>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -117,25 +117,44 @@ result = ds.fit(
|
|
|
117
117
|
### Inspect results
|
|
118
118
|
|
|
119
119
|
```python
|
|
120
|
-
#
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
# A concise summary is printed by the result itself
|
|
121
|
+
print(result)
|
|
122
|
+
# directedstructure.Result(n=62, samples=1000, mean_groups=3.2, mdl=412.100)
|
|
123
123
|
|
|
124
|
-
# Network-level
|
|
125
|
-
|
|
124
|
+
# Network-level posterior summary: columns parameter, mean, std, hdi_lo, hdi_hi
|
|
125
|
+
# Includes ICC (intraclass correlation) and sigma_T.
|
|
126
|
+
# Computed in C++ without materialising the full samples DataFrame.
|
|
127
|
+
npdf = result.network_properties_df() # default 90% HDI
|
|
128
|
+
npdf90 = result.network_properties_df(prob=0.90)
|
|
126
129
|
|
|
127
|
-
|
|
128
|
-
|
|
130
|
+
icc_row = npdf[npdf["parameter"] == "ICC"].iloc[0]
|
|
131
|
+
print(f"ICC = {icc_row['mean']:.3f} 90% HDI [{icc_row['hdi_lo']:.3f}, {icc_row['hdi_hi']:.3f}]")
|
|
129
132
|
|
|
130
|
-
#
|
|
131
|
-
|
|
133
|
+
# Per-node posterior summary: columns node, score_mean, score_std, group, group_score_mean
|
|
134
|
+
# For n ≤ 3000 uses the consensus partition (posterior summary);
|
|
135
|
+
# for larger networks falls back to the MDL partition with a warning.
|
|
136
|
+
node_df = result.node_properties_df() # auto
|
|
137
|
+
node_df = result.node_properties_df(partition="consensus") # always consensus
|
|
138
|
+
node_df = result.node_properties_df(partition="mdl") # always MDL (free)
|
|
139
|
+
|
|
140
|
+
print(node_df.sort_values("score_mean", ascending=False).head())
|
|
132
141
|
|
|
133
|
-
#
|
|
134
|
-
print(result.
|
|
135
|
-
|
|
142
|
+
# Minimum description length value (model comparison)
|
|
143
|
+
print(result.mdl_value)
|
|
144
|
+
|
|
145
|
+
# n×n co-assignment frequency matrix (posterior)
|
|
146
|
+
comatrix = result.coincidence_matrix()
|
|
136
147
|
|
|
137
|
-
# Full posterior samples DataFrame
|
|
148
|
+
# Full posterior samples DataFrame (all parameters + per-node groups/scores)
|
|
138
149
|
samples_df = result.samples_df()
|
|
150
|
+
|
|
151
|
+
# Posterior-predictive probability of each directed outcome between two nodes.
|
|
152
|
+
# Returns {"a_to_b": ..., "tie": ..., "b_to_a": ...}, values sum to 1.
|
|
153
|
+
p = result.direction_probabilities("Spain", "Morocco")
|
|
154
|
+
|
|
155
|
+
# Set ties_allowed=False for standard Bradley-Terry (no tie term).
|
|
156
|
+
p = result.direction_probabilities("Spain", "Morocco", ties_allowed=False)
|
|
157
|
+
# {"a_to_b": ..., "b_to_a": ...}
|
|
139
158
|
```
|
|
140
159
|
|
|
141
160
|
### Visualization
|
|
@@ -143,9 +162,24 @@ samples_df = result.samples_df()
|
|
|
143
162
|
```python
|
|
144
163
|
import matplotlib.pyplot as plt
|
|
145
164
|
|
|
165
|
+
# Node scores ranked within each inferred community (±1 std error bars,
|
|
166
|
+
# group mean line, communities labeled G1/G2/…)
|
|
146
167
|
fig, ax = ds.plot_node_properties(result)
|
|
147
168
|
plt.savefig("node_scores.png")
|
|
148
169
|
|
|
170
|
+
# Adjacency matrix of G reordered by inferred community, with colored
|
|
171
|
+
# community strips on the top and left edges.
|
|
172
|
+
# G can be any graph — it does not have to be the graph used in fit().
|
|
173
|
+
fig, ax = ds.plot_adjacency_matrix(result, G, cmap="Greens", title="Network")
|
|
174
|
+
plt.savefig("adjacency.png")
|
|
175
|
+
|
|
176
|
+
# Compose both panels side by side
|
|
177
|
+
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
|
|
178
|
+
ds.plot_node_properties(result, ax=axes[0])
|
|
179
|
+
ds.plot_adjacency_matrix(result, G, ax=axes[1])
|
|
180
|
+
fig.tight_layout()
|
|
181
|
+
plt.savefig("combined.png")
|
|
182
|
+
|
|
149
183
|
fig, ax = ds.plot_MCMC_entropy(result) # convergence diagnostic
|
|
150
184
|
```
|
|
151
185
|
|
|
@@ -85,25 +85,44 @@ result = ds.fit(
|
|
|
85
85
|
### Inspect results
|
|
86
86
|
|
|
87
87
|
```python
|
|
88
|
-
#
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
# A concise summary is printed by the result itself
|
|
89
|
+
print(result)
|
|
90
|
+
# directedstructure.Result(n=62, samples=1000, mean_groups=3.2, mdl=412.100)
|
|
91
91
|
|
|
92
|
-
# Network-level
|
|
93
|
-
|
|
92
|
+
# Network-level posterior summary: columns parameter, mean, std, hdi_lo, hdi_hi
|
|
93
|
+
# Includes ICC (intraclass correlation) and sigma_T.
|
|
94
|
+
# Computed in C++ without materialising the full samples DataFrame.
|
|
95
|
+
npdf = result.network_properties_df() # default 90% HDI
|
|
96
|
+
npdf90 = result.network_properties_df(prob=0.90)
|
|
94
97
|
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
icc_row = npdf[npdf["parameter"] == "ICC"].iloc[0]
|
|
99
|
+
print(f"ICC = {icc_row['mean']:.3f} 90% HDI [{icc_row['hdi_lo']:.3f}, {icc_row['hdi_hi']:.3f}]")
|
|
97
100
|
|
|
98
|
-
#
|
|
99
|
-
|
|
101
|
+
# Per-node posterior summary: columns node, score_mean, score_std, group, group_score_mean
|
|
102
|
+
# For n ≤ 3000 uses the consensus partition (posterior summary);
|
|
103
|
+
# for larger networks falls back to the MDL partition with a warning.
|
|
104
|
+
node_df = result.node_properties_df() # auto
|
|
105
|
+
node_df = result.node_properties_df(partition="consensus") # always consensus
|
|
106
|
+
node_df = result.node_properties_df(partition="mdl") # always MDL (free)
|
|
107
|
+
|
|
108
|
+
print(node_df.sort_values("score_mean", ascending=False).head())
|
|
100
109
|
|
|
101
|
-
#
|
|
102
|
-
print(result.
|
|
103
|
-
|
|
110
|
+
# Minimum description length value (model comparison)
|
|
111
|
+
print(result.mdl_value)
|
|
112
|
+
|
|
113
|
+
# n×n co-assignment frequency matrix (posterior)
|
|
114
|
+
comatrix = result.coincidence_matrix()
|
|
104
115
|
|
|
105
|
-
# Full posterior samples DataFrame
|
|
116
|
+
# Full posterior samples DataFrame (all parameters + per-node groups/scores)
|
|
106
117
|
samples_df = result.samples_df()
|
|
118
|
+
|
|
119
|
+
# Posterior-predictive probability of each directed outcome between two nodes.
|
|
120
|
+
# Returns {"a_to_b": ..., "tie": ..., "b_to_a": ...}, values sum to 1.
|
|
121
|
+
p = result.direction_probabilities("Spain", "Morocco")
|
|
122
|
+
|
|
123
|
+
# Set ties_allowed=False for standard Bradley-Terry (no tie term).
|
|
124
|
+
p = result.direction_probabilities("Spain", "Morocco", ties_allowed=False)
|
|
125
|
+
# {"a_to_b": ..., "b_to_a": ...}
|
|
107
126
|
```
|
|
108
127
|
|
|
109
128
|
### Visualization
|
|
@@ -111,9 +130,24 @@ samples_df = result.samples_df()
|
|
|
111
130
|
```python
|
|
112
131
|
import matplotlib.pyplot as plt
|
|
113
132
|
|
|
133
|
+
# Node scores ranked within each inferred community (±1 std error bars,
|
|
134
|
+
# group mean line, communities labeled G1/G2/…)
|
|
114
135
|
fig, ax = ds.plot_node_properties(result)
|
|
115
136
|
plt.savefig("node_scores.png")
|
|
116
137
|
|
|
138
|
+
# Adjacency matrix of G reordered by inferred community, with colored
|
|
139
|
+
# community strips on the top and left edges.
|
|
140
|
+
# G can be any graph — it does not have to be the graph used in fit().
|
|
141
|
+
fig, ax = ds.plot_adjacency_matrix(result, G, cmap="Greens", title="Network")
|
|
142
|
+
plt.savefig("adjacency.png")
|
|
143
|
+
|
|
144
|
+
# Compose both panels side by side
|
|
145
|
+
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
|
|
146
|
+
ds.plot_node_properties(result, ax=axes[0])
|
|
147
|
+
ds.plot_adjacency_matrix(result, G, ax=axes[1])
|
|
148
|
+
fig.tight_layout()
|
|
149
|
+
plt.savefig("combined.png")
|
|
150
|
+
|
|
117
151
|
fig, ax = ds.plot_MCMC_entropy(result) # convergence diagnostic
|
|
118
152
|
```
|
|
119
153
|
|
|
@@ -78,6 +78,9 @@ public:
|
|
|
78
78
|
fill("H_A_G_b", [](const SampleState& s){ return s.H_A_G_b; });
|
|
79
79
|
fill("H_Adir_G_A_s_nu", [](const SampleState& s){ return s.H_Adir_G_A_s_nu; });
|
|
80
80
|
fill("H_s_G_b_depths", [](const SampleState& s){ return s.H_s_G_b_depths; });
|
|
81
|
+
fill("two_layer_mode", [](const SampleState& s){ return s.two_layer_mode; });
|
|
82
|
+
fill("weighted_SBM", [](const SampleState& s){ return s.weighted_SBM; });
|
|
83
|
+
fill("H_W", [](const SampleState& s){ return s.H_W; });
|
|
81
84
|
fill("chain_id", [](const SampleState& s){ return s.chain_id; });
|
|
82
85
|
fill("beta", [](const SampleState& s){ return s.beta; });
|
|
83
86
|
|
|
@@ -100,12 +103,104 @@ public:
|
|
|
100
103
|
return d;
|
|
101
104
|
}
|
|
102
105
|
|
|
106
|
+
py::dict network_properties_dict(double prob = 0.90) const {
|
|
107
|
+
auto r = FitResult::network_properties(prob);
|
|
108
|
+
py::dict d;
|
|
109
|
+
d["parameter"] = py::cast(r.parameter);
|
|
110
|
+
d["mean"] = py::cast(r.mean);
|
|
111
|
+
d["std"] = py::cast(r.std);
|
|
112
|
+
d["hdi_lo"] = py::cast(r.hdi_lo);
|
|
113
|
+
d["hdi_hi"] = py::cast(r.hdi_hi);
|
|
114
|
+
return d;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
py::dict node_properties_dict(const std::string& mode = "auto") const {
|
|
118
|
+
auto r = FitResult::node_properties(mode);
|
|
119
|
+
py::dict d;
|
|
120
|
+
d["mode_used"] = py::cast(r.mode_used);
|
|
121
|
+
d["score_mean"] = py::cast(r.score_mean);
|
|
122
|
+
d["score_std"] = py::cast(r.score_std);
|
|
123
|
+
d["group"] = py::cast(r.group);
|
|
124
|
+
d["group_score_mean"] = py::cast(r.group_score_mean);
|
|
125
|
+
return d;
|
|
126
|
+
}
|
|
127
|
+
|
|
103
128
|
py::object coincidence_matrix_numpy() const {
|
|
104
129
|
auto flat = coincidence_matrix();
|
|
105
130
|
int n = num_nodes_;
|
|
106
131
|
py::module_ np = py::module_::import("numpy");
|
|
107
132
|
return np.attr("reshape")(py::cast(flat), py::make_tuple(n, n));
|
|
108
133
|
}
|
|
134
|
+
|
|
135
|
+
py::tuple getstate() const {
|
|
136
|
+
py::list lst;
|
|
137
|
+
for (const auto& s : samples_) {
|
|
138
|
+
py::dict d;
|
|
139
|
+
d["chain_id"] = s.chain_id;
|
|
140
|
+
d["beta"] = s.beta;
|
|
141
|
+
d["partition"] = s.partition;
|
|
142
|
+
d["scores"] = s.scores;
|
|
143
|
+
d["num_groups"] = s.num_groups;
|
|
144
|
+
d["density_in"] = s.density_in;
|
|
145
|
+
d["density_out"] = s.density_out;
|
|
146
|
+
d["variation_in"] = s.variation_in;
|
|
147
|
+
d["variation_out"] = s.variation_out;
|
|
148
|
+
d["degree_correction"] = s.degree_correction;
|
|
149
|
+
d["individual_depth"] = s.individual_depth;
|
|
150
|
+
d["group_depth"] = s.group_depth;
|
|
151
|
+
d["ties_parameter"] = s.ties_parameter;
|
|
152
|
+
d["H_A_b"] = s.H_A_b;
|
|
153
|
+
d["H_b"] = s.H_b;
|
|
154
|
+
d["H_M"] = s.H_M;
|
|
155
|
+
d["H_k"] = s.H_k;
|
|
156
|
+
d["H_A_G_k_M_b"] = s.H_A_G_k_M_b;
|
|
157
|
+
d["H_A_G_b"] = s.H_A_G_b;
|
|
158
|
+
d["H_Adir_G_A_s_nu"] = s.H_Adir_G_A_s_nu;
|
|
159
|
+
d["H_s_G_b_depths"] = s.H_s_G_b_depths;
|
|
160
|
+
d["two_layer_mode"] = s.two_layer_mode;
|
|
161
|
+
d["weighted_SBM"] = s.weighted_SBM;
|
|
162
|
+
d["H_W"] = s.H_W;
|
|
163
|
+
lst.append(d);
|
|
164
|
+
}
|
|
165
|
+
return py::make_tuple(num_nodes_, lst);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
static FitResultNative setstate(py::tuple state) {
|
|
169
|
+
int num_nodes = state[0].cast<int>();
|
|
170
|
+
auto lst = state[1].cast<py::list>();
|
|
171
|
+
std::vector<SampleState> samples;
|
|
172
|
+
samples.reserve(py::len(lst));
|
|
173
|
+
for (auto item : lst) {
|
|
174
|
+
auto d = item.cast<py::dict>();
|
|
175
|
+
SampleState s;
|
|
176
|
+
s.chain_id = d["chain_id"].cast<int>();
|
|
177
|
+
s.beta = d["beta"].cast<double>();
|
|
178
|
+
s.partition = d["partition"].cast<std::vector<int>>();
|
|
179
|
+
s.scores = d["scores"].cast<std::vector<double>>();
|
|
180
|
+
s.num_groups = d["num_groups"].cast<int>();
|
|
181
|
+
s.density_in = d["density_in"].cast<double>();
|
|
182
|
+
s.density_out = d["density_out"].cast<double>();
|
|
183
|
+
s.variation_in = d["variation_in"].cast<double>();
|
|
184
|
+
s.variation_out = d["variation_out"].cast<double>();
|
|
185
|
+
s.degree_correction = d["degree_correction"].cast<double>();
|
|
186
|
+
s.individual_depth = d["individual_depth"].cast<double>();
|
|
187
|
+
s.group_depth = d["group_depth"].cast<double>();
|
|
188
|
+
s.ties_parameter = d["ties_parameter"].cast<double>();
|
|
189
|
+
s.H_A_b = d["H_A_b"].cast<double>();
|
|
190
|
+
s.H_b = d["H_b"].cast<double>();
|
|
191
|
+
s.H_M = d["H_M"].cast<double>();
|
|
192
|
+
s.H_k = d["H_k"].cast<double>();
|
|
193
|
+
s.H_A_G_k_M_b = d["H_A_G_k_M_b"].cast<double>();
|
|
194
|
+
s.H_A_G_b = d["H_A_G_b"].cast<double>();
|
|
195
|
+
s.H_Adir_G_A_s_nu = d["H_Adir_G_A_s_nu"].cast<double>();
|
|
196
|
+
s.H_s_G_b_depths = d["H_s_G_b_depths"].cast<double>();
|
|
197
|
+
s.two_layer_mode = d.contains("two_layer_mode") ? d["two_layer_mode"].cast<bool>() : false;
|
|
198
|
+
s.weighted_SBM = d.contains("weighted_SBM") ? d["weighted_SBM"].cast<bool>() : false;
|
|
199
|
+
s.H_W = d.contains("H_W") ? d["H_W"].cast<double>() : 0.0;
|
|
200
|
+
samples.push_back(std::move(s));
|
|
201
|
+
}
|
|
202
|
+
return FitResultNative(FitResult(num_nodes, std::move(samples)));
|
|
203
|
+
}
|
|
109
204
|
};
|
|
110
205
|
|
|
111
206
|
// ---------------------------------------------------------------------------
|
|
@@ -116,6 +211,7 @@ PYBIND11_MODULE(MCMC_core, m) {
|
|
|
116
211
|
// --- MCMC class (kept for testing / advanced use) ---
|
|
117
212
|
py::class_<MCMC>(m, "MCMC")
|
|
118
213
|
.def(py::init<
|
|
214
|
+
std::vector<std::unordered_map<int, double>>,
|
|
119
215
|
std::vector<std::unordered_map<int, double>>,
|
|
120
216
|
std::vector<std::unordered_map<int, double>>,
|
|
121
217
|
std::vector<int>,
|
|
@@ -139,9 +235,11 @@ PYBIND11_MODULE(MCMC_core, m) {
|
|
|
139
235
|
const bool&,
|
|
140
236
|
int,
|
|
141
237
|
double,
|
|
142
|
-
double
|
|
238
|
+
double,
|
|
239
|
+
bool> (),
|
|
143
240
|
py::arg("wins"),
|
|
144
241
|
py::arg("ties"),
|
|
242
|
+
py::arg("group_adj") = std::vector<std::unordered_map<int, double>>{},
|
|
145
243
|
py::arg("partition"),
|
|
146
244
|
py::arg("partition_fixed") = false,
|
|
147
245
|
py::arg("scores"),
|
|
@@ -163,7 +261,8 @@ PYBIND11_MODULE(MCMC_core, m) {
|
|
|
163
261
|
py::arg("ties_parameter_fixed") = false,
|
|
164
262
|
py::arg("seed") = 0,
|
|
165
263
|
py::arg("density_in") = -1.0,
|
|
166
|
-
py::arg("density_out") = -1.0
|
|
264
|
+
py::arg("density_out") = -1.0,
|
|
265
|
+
py::arg("weighted_SBM") = false)
|
|
167
266
|
.def_readonly("num_groups", &MCMC::num_groups)
|
|
168
267
|
.def_readonly("partition", &MCMC::partition)
|
|
169
268
|
.def_readonly("scores", &MCMC::scores)
|
|
@@ -181,8 +280,10 @@ PYBIND11_MODULE(MCMC_core, m) {
|
|
|
181
280
|
.def_readonly("H_k", &MCMC::H_k)
|
|
182
281
|
.def_readonly("H_A_G_k_M_b", &MCMC::H_A_G_k_M_b)
|
|
183
282
|
.def_readonly("H_A_G_b", &MCMC::H_A_G_b)
|
|
283
|
+
.def_readonly("H_W", &MCMC::H_W)
|
|
184
284
|
.def_readonly("H_Adir_G_A_s_nu", &MCMC::H_Adir_G_A_s_nu)
|
|
185
285
|
.def_readonly("H_s_G_b_depths", &MCMC::H_s_G_b_depths)
|
|
286
|
+
.def_readonly("weighted_SBM", &MCMC::weighted_SBM)
|
|
186
287
|
.def_readonly("time_H_k", &MCMC::time_H_k)
|
|
187
288
|
.def_readonly("time_H_M", &MCMC::time_H_M)
|
|
188
289
|
.def_readonly("time_H_A_G_k_M_b", &MCMC::time_H_A_G_k_M_b)
|
|
@@ -191,6 +292,7 @@ PYBIND11_MODULE(MCMC_core, m) {
|
|
|
191
292
|
.def("MCMC_sweep", &MCMC::MCMC_sweep,
|
|
192
293
|
py::arg("beta") = 1.0,
|
|
193
294
|
py::arg("num_sweeps") = 1)
|
|
295
|
+
.def("calculate_H_M", &MCMC::calculate_H_M)
|
|
194
296
|
.def("change_parameter", &MCMC::change_parameter,
|
|
195
297
|
py::arg("param_name"),
|
|
196
298
|
py::arg("new_value"));
|
|
@@ -204,13 +306,26 @@ PYBIND11_MODULE(MCMC_core, m) {
|
|
|
204
306
|
.def("coincidence_matrix", &FitResultNative::coincidence_matrix_numpy)
|
|
205
307
|
.def("score_means", &FitResultNative::score_means)
|
|
206
308
|
.def("score_stds", &FitResultNative::score_stds)
|
|
309
|
+
.def("parameter_means", &FitResultNative::parameter_means)
|
|
310
|
+
.def("parameter_stds", &FitResultNative::parameter_stds)
|
|
311
|
+
.def("direction_probabilities", &FitResultNative::direction_probabilities,
|
|
312
|
+
py::arg("node_a"), py::arg("node_b"), py::arg("ties_allowed") = true)
|
|
313
|
+
.def("network_properties", &FitResultNative::network_properties_dict,
|
|
314
|
+
py::arg("prob") = 0.90)
|
|
315
|
+
.def("node_properties", &FitResultNative::node_properties_dict,
|
|
316
|
+
py::arg("mode") = "auto")
|
|
207
317
|
.def("samples_df", &FitResultNative::samples_df)
|
|
208
|
-
.def("summary", &FitResultNative::summary)
|
|
318
|
+
.def("summary", &FitResultNative::summary)
|
|
319
|
+
.def("__getstate__", &FitResultNative::getstate)
|
|
320
|
+
.def("__setstate__", [](FitResultNative& self, py::tuple state) {
|
|
321
|
+
new (&self) FitResultNative(FitResultNative::setstate(state));
|
|
322
|
+
});
|
|
209
323
|
|
|
210
324
|
// --- fit_backend: thin type-conversion wrapper around the C++ core fit() ---
|
|
211
325
|
m.def("fit_backend", [](
|
|
212
326
|
std::vector<std::unordered_map<int, double>> wins,
|
|
213
327
|
std::vector<std::unordered_map<int, double>> ties,
|
|
328
|
+
std::vector<std::unordered_map<int, double>> group_adj,
|
|
214
329
|
std::vector<int> initial_partition,
|
|
215
330
|
bool partition_fixed,
|
|
216
331
|
std::vector<double> initial_scores,
|
|
@@ -236,7 +351,8 @@ PYBIND11_MODULE(MCMC_core, m) {
|
|
|
236
351
|
int num_tempering_chains,
|
|
237
352
|
int seed,
|
|
238
353
|
double timeout,
|
|
239
|
-
bool show_progress
|
|
354
|
+
bool show_progress,
|
|
355
|
+
bool weighted_SBM
|
|
240
356
|
) {
|
|
241
357
|
auto to_opt_d = [](py::object o) -> std::optional<double> {
|
|
242
358
|
return o.is_none() ? std::nullopt : std::optional<double>(py::cast<double>(o));
|
|
@@ -248,6 +364,7 @@ PYBIND11_MODULE(MCMC_core, m) {
|
|
|
248
364
|
FitParams params;
|
|
249
365
|
params.wins = std::move(wins);
|
|
250
366
|
params.ties = std::move(ties);
|
|
367
|
+
params.group_adj = std::move(group_adj);
|
|
251
368
|
params.initial_partition = std::move(initial_partition);
|
|
252
369
|
params.partition_fixed = partition_fixed;
|
|
253
370
|
params.initial_scores = std::move(initial_scores);
|
|
@@ -273,41 +390,89 @@ PYBIND11_MODULE(MCMC_core, m) {
|
|
|
273
390
|
params.num_tempering_chains = num_tempering_chains;
|
|
274
391
|
params.seed = seed;
|
|
275
392
|
params.timeout = timeout;
|
|
393
|
+
params.weighted_SBM = weighted_SBM;
|
|
276
394
|
|
|
277
|
-
// Progress reporting:
|
|
395
|
+
// Progress reporting: phase-aware stderr display (pystan style).
|
|
278
396
|
std::mutex mtx;
|
|
279
397
|
std::condition_variable cv;
|
|
280
|
-
std::atomic<int>
|
|
281
|
-
bool
|
|
398
|
+
std::atomic<int> warmup_done{0}, burn_done{0}, sample_done{0};
|
|
399
|
+
bool fit_finished = false;
|
|
282
400
|
std::thread progress_thread;
|
|
283
401
|
|
|
284
402
|
if (show_progress) {
|
|
285
|
-
params.
|
|
286
|
-
|
|
403
|
+
params.on_warmup = [&](int completed, int) {
|
|
404
|
+
warmup_done.store(completed, std::memory_order_relaxed);
|
|
287
405
|
cv.notify_one();
|
|
288
|
-
(void)total;
|
|
289
406
|
};
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
407
|
+
params.on_burn_in = [&](int completed, int) {
|
|
408
|
+
burn_done.store(completed, std::memory_order_relaxed);
|
|
409
|
+
cv.notify_one();
|
|
410
|
+
};
|
|
411
|
+
params.on_sample = [&](int completed, int) {
|
|
412
|
+
sample_done.store(completed, std::memory_order_relaxed);
|
|
413
|
+
cv.notify_one();
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
const int warmup_total = num_chains * param_warmup_sweeps;
|
|
417
|
+
const int burn_total = num_chains * burn_sweeps;
|
|
418
|
+
const int sample_total = num_chains * samples_per_chain;
|
|
419
|
+
|
|
420
|
+
progress_thread = std::thread([&, warmup_total, burn_total, sample_total]() {
|
|
421
|
+
auto emit = [](const char* label, int cur, int total) {
|
|
422
|
+
int pct = total > 0 ? static_cast<int>(100.0 * cur / total) : 100;
|
|
294
423
|
std::ostringstream ss;
|
|
295
|
-
ss << '\r' <<
|
|
296
|
-
<<
|
|
297
|
-
<<
|
|
424
|
+
ss << '\r' << std::left << std::setw(11) << label
|
|
425
|
+
<< std::right << std::setw(6) << cur << " / " << total
|
|
426
|
+
<< " [" << std::setw(3) << pct << "%]";
|
|
298
427
|
std::cerr << ss.str() << std::flush;
|
|
299
428
|
};
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
int
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
429
|
+
|
|
430
|
+
auto run_phase = [&](std::atomic<int>& counter, int total,
|
|
431
|
+
const char* label) {
|
|
432
|
+
if (total <= 0) return;
|
|
433
|
+
emit(label, 0, total);
|
|
434
|
+
int last = -1;
|
|
435
|
+
while (true) {
|
|
436
|
+
std::unique_lock<std::mutex> lock(mtx);
|
|
437
|
+
cv.wait_for(lock, std::chrono::milliseconds(100),
|
|
438
|
+
[&]{ return fit_finished || counter.load() != last; });
|
|
439
|
+
int cur = counter.load(std::memory_order_relaxed);
|
|
440
|
+
bool stopping = fit_finished;
|
|
441
|
+
lock.unlock();
|
|
442
|
+
if (cur != last) { emit(label, cur, total); last = cur; }
|
|
443
|
+
if (cur >= total || stopping) break;
|
|
444
|
+
}
|
|
445
|
+
// Only print a final line if timeout ended the phase early.
|
|
446
|
+
int final_cur = counter.load(std::memory_order_relaxed);
|
|
447
|
+
if (final_cur < total)
|
|
448
|
+
emit(label, final_cur, total);
|
|
449
|
+
std::cerr << '\n' << std::flush;
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
// Show both param-warmup and burn-in under a single "Warmup:" line.
|
|
453
|
+
const int total_warmup_combined = warmup_total + burn_total;
|
|
454
|
+
if (total_warmup_combined > 0) {
|
|
455
|
+
emit("Warmup: ", 0, total_warmup_combined);
|
|
456
|
+
int last = -1;
|
|
457
|
+
while (true) {
|
|
458
|
+
std::unique_lock<std::mutex> lock(mtx);
|
|
459
|
+
int cur = warmup_done.load(std::memory_order_relaxed)
|
|
460
|
+
+ burn_done.load(std::memory_order_relaxed);
|
|
461
|
+
cv.wait_for(lock, std::chrono::milliseconds(100),
|
|
462
|
+
[&]{ return fit_finished || cur != last; });
|
|
463
|
+
cur = warmup_done.load(std::memory_order_relaxed)
|
|
464
|
+
+ burn_done.load(std::memory_order_relaxed);
|
|
465
|
+
bool stopping = fit_finished;
|
|
466
|
+
lock.unlock();
|
|
467
|
+
if (cur != last) { emit("Warmup: ", cur, total_warmup_combined); last = cur; }
|
|
468
|
+
if (cur >= total_warmup_combined || stopping) break;
|
|
469
|
+
}
|
|
470
|
+
int final_cur = warmup_done.load() + burn_done.load();
|
|
471
|
+
if (final_cur < total_warmup_combined)
|
|
472
|
+
emit("Warmup: ", final_cur, total_warmup_combined);
|
|
473
|
+
std::cerr << '\n' << std::flush;
|
|
310
474
|
}
|
|
475
|
+
run_phase(sample_done, sample_total, "Sampling: ");
|
|
311
476
|
});
|
|
312
477
|
}
|
|
313
478
|
|
|
@@ -317,16 +482,16 @@ PYBIND11_MODULE(MCMC_core, m) {
|
|
|
317
482
|
}();
|
|
318
483
|
|
|
319
484
|
if (progress_thread.joinable()) {
|
|
320
|
-
{ std::lock_guard<std::mutex> lock(mtx);
|
|
485
|
+
{ std::lock_guard<std::mutex> lock(mtx); fit_finished = true; }
|
|
321
486
|
cv.notify_all();
|
|
322
487
|
progress_thread.join();
|
|
323
|
-
if (show_progress) std::cerr << '\n' << std::flush;
|
|
324
488
|
}
|
|
325
489
|
|
|
326
490
|
return FitResultNative(std::move(result));
|
|
327
491
|
},
|
|
328
492
|
py::arg("wins"),
|
|
329
493
|
py::arg("ties"),
|
|
494
|
+
py::arg("group_adj") = std::vector<std::unordered_map<int, double>>{},
|
|
330
495
|
py::arg("initial_partition"),
|
|
331
496
|
py::arg("partition_fixed") = false,
|
|
332
497
|
py::arg("initial_scores"),
|
|
@@ -352,5 +517,6 @@ PYBIND11_MODULE(MCMC_core, m) {
|
|
|
352
517
|
py::arg("num_tempering_chains") = 1,
|
|
353
518
|
py::arg("seed") = 0,
|
|
354
519
|
py::arg("timeout") = 60.0,
|
|
355
|
-
py::arg("show_progress") = false
|
|
520
|
+
py::arg("show_progress") = false,
|
|
521
|
+
py::arg("weighted_SBM") = false);
|
|
356
522
|
}
|
|
Binary file
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import networkx as nx
|
|
4
|
+
import matplotlib.pyplot as plt
|
|
5
|
+
|
|
6
|
+
import directedstructure as ds
|
|
7
|
+
|
|
8
|
+
here = Path(__file__).resolve().parents[1]
|
|
9
|
+
G = nx.read_gml(here / "data" / "networks" / "english-football-2025.gml", label="id")
|
|
10
|
+
|
|
11
|
+
result = ds.fit(
|
|
12
|
+
ds.Config(),
|
|
13
|
+
G,
|
|
14
|
+
num_chains=1,
|
|
15
|
+
samples_per_chain=500,
|
|
16
|
+
burn_sweeps=500,
|
|
17
|
+
seed=1,
|
|
18
|
+
show_progress=True,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
print("Nodes:", G.number_of_nodes())
|
|
22
|
+
print("Edges:", G.number_of_edges())
|
|
23
|
+
print(result)
|
|
24
|
+
|
|
25
|
+
# Network-level posterior summary: mean, std, and 90% HDI for all parameters.
|
|
26
|
+
# ICC and sigma_T are included. Computed in C++ — no samples_df() overhead.
|
|
27
|
+
print("\nNetwork properties (90% HDI):")
|
|
28
|
+
npdf = result.network_properties_df()
|
|
29
|
+
print(npdf.to_string(index=False))
|
|
30
|
+
|
|
31
|
+
# Per-node posterior summary: scores, community labels, group mean score.
|
|
32
|
+
# Uses consensus partition for n ≤ 3000 (the Bayesian posterior summary).
|
|
33
|
+
print("\nTop nodes by score:")
|
|
34
|
+
node_df = result.node_properties_df()
|
|
35
|
+
print(node_df.sort_values("score_mean", ascending=False).head().to_string(index=False))
|
|
36
|
+
|
|
37
|
+
# Minimum description length (useful for model comparison)
|
|
38
|
+
print(f"\nMDL value: {result.mdl_value:.3f}")
|
|
39
|
+
|
|
40
|
+
# Full per-sample DataFrame — only materialise when raw samples are needed
|
|
41
|
+
# (e.g. convergence diagnostics, joint distributions).
|
|
42
|
+
samples_df = result.samples_df()
|
|
43
|
+
|
|
44
|
+
# Node properties plot — scores ranked within each inferred community
|
|
45
|
+
fig_a, _ = ds.plot_node_properties(result)
|
|
46
|
+
fig_a.tight_layout()
|
|
47
|
+
plot_a = Path(__file__).resolve().parent / "node_properties.png"
|
|
48
|
+
fig_a.savefig(plot_a, dpi=150)
|
|
49
|
+
plt.close(fig_a)
|
|
50
|
+
print("Saved node properties plot to:", plot_a)
|
|
51
|
+
|
|
52
|
+
# Adjacency matrix reordered by inferred community
|
|
53
|
+
fig_b, _ = ds.plot_adjacency_matrix(result, G, title="english-football-2025")
|
|
54
|
+
plot_b = Path(__file__).resolve().parent / "adjacency.png"
|
|
55
|
+
fig_b.savefig(plot_b, dpi=150, bbox_inches="tight")
|
|
56
|
+
plt.close(fig_b)
|
|
57
|
+
print("Saved adjacency matrix to:", plot_b)
|
|
58
|
+
|
|
59
|
+
# q trajectory convergence diagnostic
|
|
60
|
+
fig, ax = plt.subplots(figsize=(8, 3))
|
|
61
|
+
ax.plot(samples_df.index, samples_df["num_groups"], linewidth=1.5)
|
|
62
|
+
ax.set_xlabel("Sample")
|
|
63
|
+
ax.set_ylabel("q = num_groups")
|
|
64
|
+
ax.set_title("q trajectory")
|
|
65
|
+
fig.tight_layout()
|
|
66
|
+
plot_path = Path(__file__).resolve().parent / "q_trajectory.png"
|
|
67
|
+
fig.savefig(plot_path, dpi=150)
|
|
68
|
+
plt.close(fig)
|
|
69
|
+
print("Saved q trajectory to:", plot_path)
|
|
Binary file
|
|
Binary file
|
|
@@ -8,7 +8,7 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
from ._version import version as __version__
|
|
10
10
|
from .core import Config, Result, fit
|
|
11
|
-
from .input_output import plot_node_properties, plot_MCMC_entropy, write_gml_with_inferences
|
|
11
|
+
from .input_output import plot_node_properties, plot_adjacency_matrix, plot_MCMC_entropy, write_gml_with_inferences
|
|
12
12
|
|
|
13
13
|
__all__ = [
|
|
14
14
|
"__version__",
|
|
@@ -16,6 +16,7 @@ __all__ = [
|
|
|
16
16
|
"Result",
|
|
17
17
|
"fit",
|
|
18
18
|
"plot_node_properties",
|
|
19
|
+
"plot_adjacency_matrix",
|
|
19
20
|
"plot_MCMC_entropy",
|
|
20
21
|
"write_gml_with_inferences",
|
|
21
22
|
]
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.
|
|
22
|
-
__version_tuple__ = version_tuple = (0,
|
|
21
|
+
__version__ = version = '0.3.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 3, 0)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g297119596'
|