directedstructure 0.1.1__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.
Files changed (109) hide show
  1. directedstructure-0.3.0/.github/workflows/release.yml +79 -0
  2. {directedstructure-0.1.1 → directedstructure-0.3.0}/.gitignore +2 -0
  3. directedstructure-0.3.0/CMakeLists.txt +66 -0
  4. directedstructure-0.3.0/Makefile +102 -0
  5. directedstructure-0.3.0/PKG-INFO +217 -0
  6. directedstructure-0.3.0/README.md +185 -0
  7. {directedstructure-0.1.1 → directedstructure-0.3.0}/TODO.md +47 -1
  8. directedstructure-0.3.0/bindings/python/bindings.cpp +522 -0
  9. directedstructure-0.3.0/examples/data/networks/championship_2021.gml +6196 -0
  10. directedstructure-0.3.0/examples/data/networks/english-football-2025.gml +59986 -0
  11. directedstructure-0.3.0/examples/python/adjacency.png +0 -0
  12. directedstructure-0.3.0/examples/python/example.py +69 -0
  13. directedstructure-0.3.0/examples/python/node_properties.png +0 -0
  14. directedstructure-0.3.0/examples/python/q_trajectory.png +0 -0
  15. directedstructure-0.3.0/node_properties.csv +457 -0
  16. {directedstructure-0.1.1 → directedstructure-0.3.0}/pyproject.toml +21 -13
  17. directedstructure-0.3.0/python/directedstructure/src/directedstructure/__init__.py +22 -0
  18. directedstructure-0.3.0/python/directedstructure/src/directedstructure/_version.py +24 -0
  19. directedstructure-0.3.0/python/directedstructure/src/directedstructure/core.py +731 -0
  20. directedstructure-0.3.0/python/directedstructure/src/directedstructure/input_output.py +1013 -0
  21. {directedstructure-0.1.1 → directedstructure-0.3.0}/src/MCMC_bookkeeping.cpp +109 -72
  22. directedstructure-0.3.0/src/MCMC_core.cpp +591 -0
  23. {directedstructure-0.1.1 → directedstructure-0.3.0}/src/MCMC_core.h +33 -54
  24. {directedstructure-0.1.1 → directedstructure-0.3.0}/src/MCMC_proposals.cpp +374 -173
  25. directedstructure-0.3.0/src/fit.cpp +202 -0
  26. directedstructure-0.3.0/src/fit.h +61 -0
  27. directedstructure-0.3.0/src/fit_result.cpp +304 -0
  28. directedstructure-0.3.0/src/fit_result.h +80 -0
  29. directedstructure-0.3.0/src/globals.cpp +16 -0
  30. {directedstructure-0.1.1 → directedstructure-0.3.0}/src/globals.h +13 -15
  31. {directedstructure-0.1.1 → directedstructure-0.3.0}/src/helpers.cpp +10 -4
  32. directedstructure-0.3.0/tests/README.md +125 -0
  33. directedstructure-0.3.0/tests/data/inputs/wins_ties_3node_point_eval.csv +12 -0
  34. directedstructure-0.3.0/tests/data/outputs/triangle_coupled_coincidence.csv +3 -0
  35. directedstructure-0.3.0/tests/data/outputs/triangle_independent_coincidence.csv +3 -0
  36. directedstructure-0.3.0/tests/data/outputs/wins_ties_3node_coincidence.csv +3 -0
  37. directedstructure-0.3.0/tests/data/outputs/wins_ties_3node_point_eval_components.csv +2 -0
  38. directedstructure-0.3.0/tests/mcmc_core_test.cpp +470 -0
  39. directedstructure-0.3.0/tests/python/test_hw_routing.py +101 -0
  40. directedstructure-0.3.0/tests/python/test_hw_sensitivity.py +117 -0
  41. directedstructure-0.3.0/tests/python/test_package.py +921 -0
  42. directedstructure-0.3.0/tests/python/test_weighted_sbm_model_selection.py +234 -0
  43. directedstructure-0.1.1/.github/workflows/cd.yml +0 -95
  44. directedstructure-0.1.1/.github/workflows/ci.yml +0 -77
  45. directedstructure-0.1.1/.github/workflows/release.yml +0 -114
  46. directedstructure-0.1.1/CMakeLists.txt +0 -25
  47. directedstructure-0.1.1/Makefile +0 -62
  48. directedstructure-0.1.1/PKG-INFO +0 -120
  49. directedstructure-0.1.1/README.md +0 -88
  50. directedstructure-0.1.1/examples/consensus.py +0 -27
  51. directedstructure-0.1.1/examples/data/networks/friends_raw.gml +0 -1079
  52. directedstructure-0.1.1/examples/data/networks/highschool_30_raw.gml +0 -12183
  53. directedstructure-0.1.1/examples/data/networks/highschool_51_raw.gml +0 -6307
  54. directedstructure-0.1.1/examples/data/networks/highschool_84_raw.gml +0 -15219
  55. directedstructure-0.1.1/examples/data/process_friendship_nominations.py +0 -42
  56. directedstructure-0.1.1/examples/data/samples/friends.csv +0 -4491
  57. directedstructure-0.1.1/examples/data/samples/highschool_51.csv +0 -1075
  58. directedstructure-0.1.1/examples/data/take_samples.py +0 -19
  59. directedstructure-0.1.1/examples/data/test_2.gml +0 -21
  60. directedstructure-0.1.1/examples/prediction.py +0 -1
  61. directedstructure-0.1.1/examples/sampling.py +0 -1
  62. directedstructure-0.1.1/examples/speed_test.py +0 -1
  63. directedstructure-0.1.1/examples/testing copy.png +0 -0
  64. directedstructure-0.1.1/examples/testing.png +0 -0
  65. directedstructure-0.1.1/examples/testing.py +0 -96
  66. directedstructure-0.1.1/examples/visualization.py +0 -12
  67. directedstructure-0.1.1/src/MCMC_core.cpp +0 -728
  68. directedstructure-0.1.1/src/MCMC_merge_split.cpp +0 -738
  69. directedstructure-0.1.1/src/consensus.cpp +0 -132
  70. directedstructure-0.1.1/src/consensus.h +0 -27
  71. directedstructure-0.1.1/src/directedstructure/__init__.py +0 -21
  72. directedstructure-0.1.1/src/directedstructure/_version.py +0 -34
  73. directedstructure-0.1.1/src/directedstructure/_version.pyi +0 -4
  74. directedstructure-0.1.1/src/directedstructure/bayes_factor.py +0 -101
  75. directedstructure-0.1.1/src/directedstructure/clusterings.py +0 -130
  76. directedstructure-0.1.1/src/directedstructure/core.py +0 -571
  77. directedstructure-0.1.1/src/directedstructure/input_output.py +0 -488
  78. directedstructure-0.1.1/src/directedstructure/prediction.py +0 -126
  79. directedstructure-0.1.1/src/directedstructure/sample.py +0 -338
  80. directedstructure-0.1.1/src/directedstructure/sample_analysis.py +0 -126
  81. directedstructure-0.1.1/tests/mcmc_core_test.cpp +0 -515
  82. directedstructure-0.1.1/tests/test_package.py +0 -9
  83. {directedstructure-0.1.1 → directedstructure-0.3.0}/.copier-answers.yml +0 -0
  84. {directedstructure-0.1.1 → directedstructure-0.3.0}/.git_archival.txt +0 -0
  85. {directedstructure-0.1.1 → directedstructure-0.3.0}/.gitattributes +0 -0
  86. {directedstructure-0.1.1 → directedstructure-0.3.0}/.github/CONTRIBUTING.md +0 -0
  87. {directedstructure-0.1.1 → directedstructure-0.3.0}/.github/dependabot.yml +0 -0
  88. {directedstructure-0.1.1 → directedstructure-0.3.0}/.github/release.yml +0 -0
  89. {directedstructure-0.1.1 → directedstructure-0.3.0}/.pre-commit-config.yaml +0 -0
  90. {directedstructure-0.1.1 → directedstructure-0.3.0}/.readthedocs.yaml +0 -0
  91. {directedstructure-0.1.1 → directedstructure-0.3.0}/LICENSE +0 -0
  92. {directedstructure-0.1.1 → directedstructure-0.3.0}/docs/conf.py +0 -0
  93. {directedstructure-0.1.1 → directedstructure-0.3.0}/docs/examples/prediction.md +0 -0
  94. {directedstructure-0.1.1 → directedstructure-0.3.0}/docs/examples/speed_test.md +0 -0
  95. {directedstructure-0.1.1 → directedstructure-0.3.0}/docs/examples/visualization.md +0 -0
  96. {directedstructure-0.1.1 → directedstructure-0.3.0}/docs/index.md +0 -0
  97. {directedstructure-0.1.1 → directedstructure-0.3.0}/docs/modules.md +0 -0
  98. {directedstructure-0.1.1 → directedstructure-0.3.0}/examples/data/networks/business_depts.gml +0 -0
  99. {directedstructure-0.1.1 → directedstructure-0.3.0}/examples/data/networks/friends.gml +0 -0
  100. {directedstructure-0.1.1 → directedstructure-0.3.0}/examples/data/networks/highschool_30.gml +0 -0
  101. {directedstructure-0.1.1 → directedstructure-0.3.0}/examples/data/networks/highschool_51.gml +0 -0
  102. {directedstructure-0.1.1 → directedstructure-0.3.0}/examples/data/networks/highschool_84.gml +0 -0
  103. {directedstructure-0.1.1 → directedstructure-0.3.0}/noxfile.py +0 -0
  104. {directedstructure-0.1.1 → directedstructure-0.3.0/python/directedstructure}/src/directedstructure/MCMC_core.pyi +0 -0
  105. {directedstructure-0.1.1 → directedstructure-0.3.0/python/directedstructure}/src/directedstructure/_core.pyi +0 -0
  106. {directedstructure-0.1.1 → directedstructure-0.3.0/python/directedstructure}/src/directedstructure/_graph_cache.py +0 -0
  107. {directedstructure-0.1.1 → directedstructure-0.3.0/python/directedstructure}/src/directedstructure/_util.py +0 -0
  108. {directedstructure-0.1.1 → directedstructure-0.3.0/python/directedstructure}/src/directedstructure/py.typed +0 -0
  109. {directedstructure-0.1.1 → directedstructure-0.3.0}/src/helpers.h +0 -0
@@ -0,0 +1,79 @@
1
+ name: release
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ release:
6
+ types:
7
+ - published
8
+ pull_request:
9
+ paths:
10
+ - .github/workflows/release.yml
11
+
12
+ concurrency:
13
+ group: ${{ github.workflow }}-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ env:
17
+ FORCE_COLOR: 3
18
+
19
+ jobs:
20
+ make_sdist:
21
+ name: Make SDist
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+ with:
26
+ fetch-depth: 0
27
+
28
+ - name: Build SDist
29
+ run: pipx run build --sdist
30
+
31
+ - uses: actions/upload-artifact@v4
32
+ with:
33
+ name: cibw-sdist
34
+ path: dist/*.tar.gz
35
+
36
+ build_wheels:
37
+ name: Wheel on ${{ matrix.os }}
38
+ runs-on: ${{ matrix.os }}
39
+ strategy:
40
+ fail-fast: false
41
+ matrix:
42
+ os: [macos-14, ubuntu-latest, windows-latest]
43
+
44
+ steps:
45
+ - uses: actions/checkout@v4
46
+ with:
47
+ fetch-depth: 0
48
+
49
+ - name: Install cibuildwheel
50
+ run: python -m pip install cibuildwheel==2.20
51
+
52
+ - name: Build wheels
53
+ env:
54
+ CIBW_ARCHS_LINUX: x86_64
55
+ CIBW_ARCHS_WINDOWS: AMD64
56
+ run: cibuildwheel --output-dir wheelhouse
57
+
58
+ - uses: actions/upload-artifact@v4
59
+ with:
60
+ name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
61
+ path: wheelhouse/*.whl
62
+
63
+ upload_all:
64
+ needs: [build_wheels, make_sdist]
65
+ environment: pypi
66
+ permissions:
67
+ id-token: write
68
+ contents: read
69
+ runs-on: ubuntu-latest
70
+ steps:
71
+ - uses: actions/download-artifact@v4
72
+ with:
73
+ pattern: cibw-*
74
+ path: dist
75
+ merge-multiple: true
76
+
77
+ - uses: pypa/gh-action-pypi-publish@release/v1
78
+ with:
79
+ verbose: true
@@ -169,6 +169,8 @@ cython_debug/
169
169
 
170
170
  # setuptools_scm
171
171
  src/*/_version.py
172
+ python/directedstructure/src/directedstructure/_version.py
173
+ python/directedstructure/src/directedstructure/_version.pyi
172
174
 
173
175
 
174
176
  # ruff
@@ -0,0 +1,66 @@
1
+ cmake_minimum_required(VERSION 3.15...3.26)
2
+ project(${SKBUILD_PROJECT_NAME} LANGUAGES CXX)
3
+
4
+ option(BUILD_TESTING "Build Catch2-based native tests" OFF)
5
+
6
+ set(PYBIND11_FINDPYTHON ON)
7
+ find_package(pybind11 CONFIG REQUIRED)
8
+
9
+ # Build a reusable core library so other language bindings can link against it
10
+ add_library(directedstructure_core STATIC
11
+ src/MCMC_core.cpp
12
+ src/MCMC_proposals.cpp
13
+ src/MCMC_bookkeeping.cpp
14
+ src/helpers.cpp
15
+ src/globals.cpp
16
+ src/fit_result.cpp
17
+ src/fit.cpp)
18
+ target_include_directories(directedstructure_core PUBLIC ${CMAKE_SOURCE_DIR}/src)
19
+ set_target_properties(directedstructure_core PROPERTIES POSITION_INDEPENDENT_CODE ON)
20
+ target_compile_features(directedstructure_core PUBLIC cxx_std_17)
21
+ target_compile_options(directedstructure_core PRIVATE -O3 -march=native)
22
+
23
+ # Python bindings: a small pybind11 module that links to the core library
24
+ pybind11_add_module(MCMC_core MODULE bindings/python/bindings.cpp)
25
+ target_link_libraries(MCMC_core PRIVATE directedstructure_core)
26
+ target_include_directories(MCMC_core PRIVATE ${CMAKE_SOURCE_DIR}/src)
27
+ target_compile_features(MCMC_core PRIVATE cxx_std_17)
28
+ set_target_properties(
29
+ MCMC_core PROPERTIES LIBRARY_OUTPUT_DIRECTORY
30
+ ${CMAKE_SOURCE_DIR}/python/directedstructure/src/directedstructure)
31
+ install(TARGETS MCMC_core LIBRARY DESTINATION ${SKBUILD_PROJECT_NAME})
32
+
33
+ if(BUILD_TESTING)
34
+ enable_testing()
35
+
36
+ find_package(Catch2 3 QUIET)
37
+ if(NOT Catch2_FOUND)
38
+ include(FetchContent)
39
+ FetchContent_Declare(
40
+ Catch2
41
+ GIT_REPOSITORY https://github.com/catchorg/Catch2.git
42
+ GIT_TAG v3.7.1
43
+ GIT_SHALLOW TRUE
44
+ )
45
+ FetchContent_MakeAvailable(Catch2)
46
+ list(APPEND CMAKE_MODULE_PATH "${catch2_SOURCE_DIR}/extras")
47
+ endif()
48
+
49
+ add_executable(mcmc_core_test
50
+ tests/mcmc_core_test.cpp
51
+ src/MCMC_core.cpp
52
+ src/MCMC_proposals.cpp
53
+ src/MCMC_bookkeeping.cpp
54
+ src/helpers.cpp
55
+ src/globals.cpp
56
+ src/fit_result.cpp
57
+ src/fit.cpp
58
+ )
59
+ target_include_directories(mcmc_core_test PRIVATE tests src)
60
+ target_compile_features(mcmc_core_test PRIVATE cxx_std_17)
61
+ target_compile_options(mcmc_core_test PRIVATE -Wall -Wextra)
62
+ target_link_libraries(mcmc_core_test PRIVATE Catch2::Catch2WithMain)
63
+
64
+ include(CTest)
65
+ add_test(NAME mcmc_core_test COMMAND mcmc_core_test)
66
+ endif()
@@ -0,0 +1,102 @@
1
+ # Makefile to build the pybind11 extension module and the C++ test harness
2
+
3
+ PYTHON ?= python3
4
+ CXX ?= g++
5
+ BUILD_DIR := build
6
+ SRC_DIR := src
7
+ PKG_DIR := $(SRC_DIR)/directedstructure
8
+ MODULE_NAME := MCMC_core
9
+ EXT_SUFFIX := $(shell $(PYTHON) -c "import sysconfig; print(sysconfig.get_config_var('EXT_SUFFIX') or '')")
10
+ PYBIND11_INCLUDES := $(shell $(PYTHON) -m pybind11 --includes 2>/dev/null)
11
+ # Prefer embed flags so executables link against libpython; fall back to standard ldflags
12
+ PY_LDFLAGS := $(shell $(PYTHON)-config --embed --ldflags 2>/dev/null || $(PYTHON)-config --ldflags 2>/dev/null || python3-config --embed --ldflags 2>/dev/null || python3-config --ldflags 2>/dev/null || echo "")
13
+ MKDIR_P := mkdir -p
14
+
15
+ CXXFLAGS := -std=c++17 -Wall -Wextra -O3 -march=native -fPIC -I$(SRC_DIR) -DDIRECTEDSTRUCTURE_PYBIND
16
+ DEBUG_CXXFLAGS := -std=c++17 -Wall -Wextra -O0 -g -fPIC -I$(SRC_DIR) -DDIRECTEDSTRUCTURE_PYBIND
17
+ TEST_CXXFLAGS := -std=c++17 -Wall -Wextra -O3 -march=native -fPIC -I$(SRC_DIR)
18
+ TEST_DEBUG_CXXFLAGS := -std=c++17 -Wall -Wextra -O0 -g -fPIC -I$(SRC_DIR)
19
+
20
+ SOURCES := $(SRC_DIR)/MCMC_core.cpp \
21
+ $(SRC_DIR)/MCMC_proposals.cpp \
22
+ $(SRC_DIR)/MCMC_bookkeeping.cpp \
23
+ $(SRC_DIR)/MCMC_merge_split.cpp \
24
+ $(SRC_DIR)/consensus.cpp \
25
+ $(SRC_DIR)/helpers.cpp
26
+ OBJECTS := $(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/%.o,$(SOURCES))
27
+ TEST_CORE_OBJECTS := $(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/%_test.o,$(SOURCES))
28
+
29
+ TEST_SOURCES := tests/mcmc_core_test.cpp
30
+ TEST_OBJECTS := $(patsubst tests/%.cpp,$(BUILD_DIR)/tests_%.o,$(TEST_SOURCES))
31
+ DEBUG_OBJECTS := $(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/%_debug.o,$(SOURCES))
32
+ DEBUG_TEST_CORE_OBJECTS := $(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/%_test_debug.o,$(SOURCES))
33
+
34
+ MODULE_TARGET := $(PKG_DIR)/$(MODULE_NAME)$(EXT_SUFFIX)
35
+ TEST_BIN := $(BUILD_DIR)/mcmc_core_test
36
+ TEST_BIN_DEBUG := $(BUILD_DIR)/mcmc_core_test_debug
37
+ CSV_BIN := AddHealth_analysis/run_sampler_to_csv
38
+
39
+ .PHONY: all clean test csv
40
+
41
+ all: $(MODULE_TARGET) $(TEST_BIN)
42
+
43
+ $(BUILD_DIR):
44
+ $(MKDIR_P) $(BUILD_DIR)
45
+
46
+ $(PKG_DIR):
47
+ $(MKDIR_P) $(PKG_DIR)
48
+
49
+ $(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp | $(BUILD_DIR)
50
+ $(CXX) $(CXXFLAGS) $(PYBIND11_INCLUDES) -c $< -o $@
51
+
52
+ $(BUILD_DIR)/tests_%.o: tests/%.cpp | $(BUILD_DIR)
53
+ $(CXX) $(TEST_CXXFLAGS) -c $< -o $@
54
+
55
+ $(BUILD_DIR)/addhealth_%.o: AddHealth_analysis/%.cpp | $(BUILD_DIR)
56
+ $(CXX) $(TEST_CXXFLAGS) -c $< -o $@
57
+
58
+ $(BUILD_DIR)/%_test.o: $(SRC_DIR)/%.cpp | $(BUILD_DIR)
59
+ $(CXX) $(TEST_CXXFLAGS) -c $< -o $@
60
+
61
+ $(MODULE_TARGET): $(OBJECTS) | $(PKG_DIR)
62
+ $(CXX) $(CXXFLAGS) -shared $^ -o $@ $(PY_LDFLAGS)
63
+
64
+ $(TEST_BIN): $(TEST_OBJECTS) $(TEST_CORE_OBJECTS) | $(BUILD_DIR)
65
+ $(CXX) $(TEST_CXXFLAGS) $^ -o $@
66
+
67
+ $(CSV_BIN): $(BUILD_DIR)/addhealth_run_sampler_to_csv.o $(TEST_CORE_OBJECTS)
68
+ $(CXX) $(TEST_CXXFLAGS) $^ -o $@
69
+
70
+ # Debug build for valgrind
71
+ $(BUILD_DIR)/%_debug.o: $(SRC_DIR)/%.cpp | $(BUILD_DIR)
72
+ $(CXX) $(DEBUG_CXXFLAGS) $(PYBIND11_INCLUDES) -c $< -o $@
73
+
74
+ $(BUILD_DIR)/tests_%_debug.o: tests/%.cpp | $(BUILD_DIR)
75
+ $(CXX) $(TEST_DEBUG_CXXFLAGS) -c $< -o $@
76
+
77
+ $(BUILD_DIR)/%_test_debug.o: $(SRC_DIR)/%.cpp | $(BUILD_DIR)
78
+ $(CXX) $(TEST_DEBUG_CXXFLAGS) -c $< -o $@
79
+
80
+ DEBUG_TEST_OBJECTS := $(patsubst tests/%.cpp,$(BUILD_DIR)/tests_%_debug.o,$(TEST_SOURCES))
81
+
82
+ $(TEST_BIN_DEBUG): $(DEBUG_TEST_OBJECTS) $(DEBUG_TEST_CORE_OBJECTS) | $(BUILD_DIR)
83
+ $(CXX) $(TEST_DEBUG_CXXFLAGS) $(DEBUG_TEST_OBJECTS) $(DEBUG_TEST_CORE_OBJECTS) -o $@
84
+
85
+ clean:
86
+ rm -rf $(BUILD_DIR) $(MODULE_TARGET)
87
+ rm -f $(PKG_DIR)/$(MODULE_NAME)*.so $(PKG_DIR)/$(MODULE_NAME)*.pyd
88
+ rm -f $(PKG_DIR)/$(MODULE_NAME)*.dll
89
+ rm -f $(PKG_DIR)/$(MODULE_NAME)*.dylib
90
+ rm -f $(PKG_DIR)/$(MODULE_NAME)*.pdb
91
+ rm -f $(PKG_DIR)/$(MODULE_NAME)*.exp
92
+
93
+ # Run the C++ harness
94
+ test: $(TEST_BIN)
95
+ ./$(TEST_BIN)
96
+
97
+ # Build the C++ sampler-to-CSV binary only
98
+ csv: $(CSV_BIN)
99
+
100
+ # Run with valgrind (requires valgrind to be installed)
101
+ test-valgrind: $(TEST_BIN_DEBUG)
102
+ valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose ./$(TEST_BIN_DEBUG)
@@ -0,0 +1,217 @@
1
+ Metadata-Version: 2.4
2
+ Name: directedstructure
3
+ Version: 0.3.0
4
+ Summary: Infer communities, hierarchies, and their connection in directed graphs
5
+ Author-Email: Maximilian Jerdee <mjerdee@santafe.edu>
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Classifier: Development Status :: 1 - Planning
9
+ Classifier: Intended Audience :: Science/Research
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Scientific/Engineering
20
+ Classifier: Typing :: Typed
21
+ Project-URL: Homepage, https://github.com/maxjerdee/directedstructure
22
+ Project-URL: Bug Tracker, https://github.com/maxjerdee/directedstructure/issues
23
+ Project-URL: Discussions, https://github.com/maxjerdee/directedstructure/discussions
24
+ Project-URL: Changelog, https://github.com/maxjerdee/directedstructure/releases
25
+ Requires-Python: <3.14,>=3.10
26
+ Requires-Dist: matplotlib>=3.8.0
27
+ Requires-Dist: numpy>=1.26.0
28
+ Requires-Dist: pandas<3.0,>=2.2.0
29
+ Requires-Dist: networkx>=3.0
30
+ Requires-Dist: tqdm>=4.65.0
31
+ Description-Content-Type: text/markdown
32
+
33
+ # directedstructure
34
+
35
+ [![Documentation Status][rtd-badge]][rtd-link]
36
+ [![PyPI version][pypi-version]][pypi-link]
37
+ [![PyPI platforms][pypi-platforms]][pypi-link]
38
+
39
+ <!-- SPHINX-START -->
40
+
41
+ <!-- prettier-ignore-start -->
42
+ [actions-badge]: https://github.com/maxjerdee/directedstructure/workflows/CI/badge.svg
43
+ [actions-link]: https://github.com/maxjerdee/directedstructure/actions
44
+ [conda-badge]: https://img.shields.io/conda/vn/conda-forge/directedstructure
45
+ [conda-link]: https://github.com/conda-forge/directedstructure-feedstock
46
+ [github-discussions-badge]: https://img.shields.io/static/v1?label=Discussions&message=Ask&color=blue&logo=github
47
+ [github-discussions-link]: https://github.com/maxjerdee/directedstructure/discussions
48
+ [pypi-link]: https://pypi.org/project/directedstructure/
49
+ [pypi-platforms]: https://img.shields.io/pypi/pyversions/directedstructure
50
+ [pypi-version]: https://img.shields.io/pypi/v/directedstructure
51
+ [rtd-badge]: https://readthedocs.org/projects/directedstructure/badge/?version=latest
52
+ [rtd-link]: https://directedstructure.readthedocs.io/en/latest/?badge=latest
53
+
54
+ <!-- prettier-ignore-end -->
55
+
56
+ ### Infer communities, hierarchies, and their connection in directed graphs
57
+
58
+ ##### Maximilian Jerdee, Elizabeth Bruch, Mark Newman
59
+
60
+ This Python package uses Bayesian inference to identify communities and hierarchies of nodes in a directed network, as well as measure the interaction between those structures.
61
+
62
+ We model community structure using a stochastic block model, hierarchy structure with a Bradley-Terry model, and their interaction according to our work (link forthcoming).
63
+
64
+ ## Installation
65
+
66
+ ```bash
67
+ pip install directedstructure
68
+ ```
69
+
70
+ Or build locally by cloning this repository and running
71
+
72
+ ```bash
73
+ pip install .
74
+ ```
75
+
76
+ in the base directory (requires a C++ compiler).
77
+
78
+ ## Usage
79
+
80
+ The public API has three objects: `Config`, `fit`, and `Result`.
81
+
82
+ ### Load a network
83
+
84
+ ```python
85
+ import directedstructure as ds
86
+ import networkx as nx
87
+
88
+ G = nx.read_gml("examples/data/networks/friends.gml")
89
+ ```
90
+
91
+ Edges may carry a `type` attribute (`'dominant'` or `'neutral'`) to distinguish directed pairwise comparisons from symmetric ties. If no type information is provided all edges are treated as dominant.
92
+
93
+ ### Configure the model
94
+
95
+ ```python
96
+ config = ds.Config(
97
+ groups_model="general_canonical", # community structure preset
98
+ hierarchy_model="bradley_terry_ties", # auto-selected if omitted
99
+ interaction="coupled", # coupling between communities and hierarchy
100
+ )
101
+ ```
102
+
103
+ `Config` is a frozen dataclass — see the API docs for the full list of parameters (mixing variation, degree correction, individual/group depth, etc.).
104
+
105
+ ### Run inference
106
+
107
+ ```python
108
+ result = ds.fit(
109
+ config, G,
110
+ num_samples=1000, # posterior samples to collect
111
+ sweeps_per_sample=10, # MCMC sweeps between samples
112
+ timeout=60.0, # wall-clock limit in seconds
113
+ seed=42,
114
+ )
115
+ ```
116
+
117
+ ### Inspect results
118
+
119
+ ```python
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
+
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)
129
+
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}]")
132
+
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())
141
+
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()
147
+
148
+ # Full posterior samples DataFrame (all parameters + per-node groups/scores)
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": ...}
158
+ ```
159
+
160
+ ### Visualization
161
+
162
+ ```python
163
+ import matplotlib.pyplot as plt
164
+
165
+ # Node scores ranked within each inferred community (±1 std error bars,
166
+ # group mean line, communities labeled G1/G2/…)
167
+ fig, ax = ds.plot_node_properties(result)
168
+ plt.savefig("node_scores.png")
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
+
183
+ fig, ax = ds.plot_MCMC_entropy(result) # convergence diagnostic
184
+ ```
185
+
186
+ Export the graph with inferred attributes for use in Gephi or other tools:
187
+
188
+ ```python
189
+ ds.write_gml_with_inferences(G, "output.gml", result)
190
+ ```
191
+
192
+ ### Fixing model components
193
+
194
+ Pass fixed values in `Config` to test submodels. For example, to fix group structure from node attributes and infer hierarchy only:
195
+
196
+ ```python
197
+ config = ds.Config(group_attribute="department", interaction="independent")
198
+ result = ds.fit(config, G)
199
+ ```
200
+
201
+ Or fix the number of groups:
202
+
203
+ ```python
204
+ config = ds.Config(num_groups=4)
205
+ result = ds.fit(config, G)
206
+ ```
207
+
208
+ ### Parallel tempering
209
+
210
+ For complex posteriors, enable parallel tempering:
211
+
212
+ ```python
213
+ result = ds.fit(config, G, num_tempering_chains=4, beta=1.0)
214
+ ```
215
+
216
+ Further usage examples can be found in the `examples/python/` directory of the
217
+ repository and the [package documentation][rtd-link].
@@ -0,0 +1,185 @@
1
+ # directedstructure
2
+
3
+ [![Documentation Status][rtd-badge]][rtd-link]
4
+ [![PyPI version][pypi-version]][pypi-link]
5
+ [![PyPI platforms][pypi-platforms]][pypi-link]
6
+
7
+ <!-- SPHINX-START -->
8
+
9
+ <!-- prettier-ignore-start -->
10
+ [actions-badge]: https://github.com/maxjerdee/directedstructure/workflows/CI/badge.svg
11
+ [actions-link]: https://github.com/maxjerdee/directedstructure/actions
12
+ [conda-badge]: https://img.shields.io/conda/vn/conda-forge/directedstructure
13
+ [conda-link]: https://github.com/conda-forge/directedstructure-feedstock
14
+ [github-discussions-badge]: https://img.shields.io/static/v1?label=Discussions&message=Ask&color=blue&logo=github
15
+ [github-discussions-link]: https://github.com/maxjerdee/directedstructure/discussions
16
+ [pypi-link]: https://pypi.org/project/directedstructure/
17
+ [pypi-platforms]: https://img.shields.io/pypi/pyversions/directedstructure
18
+ [pypi-version]: https://img.shields.io/pypi/v/directedstructure
19
+ [rtd-badge]: https://readthedocs.org/projects/directedstructure/badge/?version=latest
20
+ [rtd-link]: https://directedstructure.readthedocs.io/en/latest/?badge=latest
21
+
22
+ <!-- prettier-ignore-end -->
23
+
24
+ ### Infer communities, hierarchies, and their connection in directed graphs
25
+
26
+ ##### Maximilian Jerdee, Elizabeth Bruch, Mark Newman
27
+
28
+ This Python package uses Bayesian inference to identify communities and hierarchies of nodes in a directed network, as well as measure the interaction between those structures.
29
+
30
+ We model community structure using a stochastic block model, hierarchy structure with a Bradley-Terry model, and their interaction according to our work (link forthcoming).
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install directedstructure
36
+ ```
37
+
38
+ Or build locally by cloning this repository and running
39
+
40
+ ```bash
41
+ pip install .
42
+ ```
43
+
44
+ in the base directory (requires a C++ compiler).
45
+
46
+ ## Usage
47
+
48
+ The public API has three objects: `Config`, `fit`, and `Result`.
49
+
50
+ ### Load a network
51
+
52
+ ```python
53
+ import directedstructure as ds
54
+ import networkx as nx
55
+
56
+ G = nx.read_gml("examples/data/networks/friends.gml")
57
+ ```
58
+
59
+ Edges may carry a `type` attribute (`'dominant'` or `'neutral'`) to distinguish directed pairwise comparisons from symmetric ties. If no type information is provided all edges are treated as dominant.
60
+
61
+ ### Configure the model
62
+
63
+ ```python
64
+ config = ds.Config(
65
+ groups_model="general_canonical", # community structure preset
66
+ hierarchy_model="bradley_terry_ties", # auto-selected if omitted
67
+ interaction="coupled", # coupling between communities and hierarchy
68
+ )
69
+ ```
70
+
71
+ `Config` is a frozen dataclass — see the API docs for the full list of parameters (mixing variation, degree correction, individual/group depth, etc.).
72
+
73
+ ### Run inference
74
+
75
+ ```python
76
+ result = ds.fit(
77
+ config, G,
78
+ num_samples=1000, # posterior samples to collect
79
+ sweeps_per_sample=10, # MCMC sweeps between samples
80
+ timeout=60.0, # wall-clock limit in seconds
81
+ seed=42,
82
+ )
83
+ ```
84
+
85
+ ### Inspect results
86
+
87
+ ```python
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
+
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)
97
+
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}]")
100
+
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())
109
+
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()
115
+
116
+ # Full posterior samples DataFrame (all parameters + per-node groups/scores)
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": ...}
126
+ ```
127
+
128
+ ### Visualization
129
+
130
+ ```python
131
+ import matplotlib.pyplot as plt
132
+
133
+ # Node scores ranked within each inferred community (±1 std error bars,
134
+ # group mean line, communities labeled G1/G2/…)
135
+ fig, ax = ds.plot_node_properties(result)
136
+ plt.savefig("node_scores.png")
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
+
151
+ fig, ax = ds.plot_MCMC_entropy(result) # convergence diagnostic
152
+ ```
153
+
154
+ Export the graph with inferred attributes for use in Gephi or other tools:
155
+
156
+ ```python
157
+ ds.write_gml_with_inferences(G, "output.gml", result)
158
+ ```
159
+
160
+ ### Fixing model components
161
+
162
+ Pass fixed values in `Config` to test submodels. For example, to fix group structure from node attributes and infer hierarchy only:
163
+
164
+ ```python
165
+ config = ds.Config(group_attribute="department", interaction="independent")
166
+ result = ds.fit(config, G)
167
+ ```
168
+
169
+ Or fix the number of groups:
170
+
171
+ ```python
172
+ config = ds.Config(num_groups=4)
173
+ result = ds.fit(config, G)
174
+ ```
175
+
176
+ ### Parallel tempering
177
+
178
+ For complex posteriors, enable parallel tempering:
179
+
180
+ ```python
181
+ result = ds.fit(config, G, num_tempering_chains=4, beta=1.0)
182
+ ```
183
+
184
+ Further usage examples can be found in the `examples/python/` directory of the
185
+ repository and the [package documentation][rtd-link].