digraphx 0.3__tar.gz → 0.5__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 (208) hide show
  1. {digraphx-0.3 → digraphx-0.5}/.github/workflows/ci.bak +3 -3
  2. {digraphx-0.3 → digraphx-0.5}/.github/workflows/multi-platforms.yml +1 -2
  3. {digraphx-0.3 → digraphx-0.5}/.github/workflows/python-app.yml +4 -5
  4. {digraphx-0.3 → digraphx-0.5}/.gitignore +4 -0
  5. digraphx-0.5/CHANGELOG.md +29 -0
  6. {digraphx-0.3/src/digraphx.egg-info → digraphx-0.5}/PKG-INFO +3 -2
  7. {digraphx-0.3 → digraphx-0.5}/docs/conf.py +14 -0
  8. digraphx-0.5/docs/examples/plot_cycle_detection.py +32 -0
  9. digraphx-0.5/docs/examples/plot_tiny_digraph.py +33 -0
  10. digraphx-0.5/docs/figures_demo.md +25 -0
  11. {digraphx-0.3 → digraphx-0.5}/docs/index.md +1 -0
  12. digraphx-0.5/experimental/__init__.py +5 -0
  13. digraphx-0.5/experimental/spare_tsv.py +190 -0
  14. digraphx-0.5/experimental/test_spare_tsv.py +170 -0
  15. digraphx-0.5/experiments/experi-descent-new.py +79 -0
  16. digraphx-0.5/experiments/experi-descent-old.py +72 -0
  17. digraphx-0.5/experiments/experi-descent.py +79 -0
  18. digraphx-0.5/experiments/experi.py +37 -0
  19. {digraphx-0.3 → digraphx-0.5}/mypy.ini +0 -3
  20. {digraphx-0.3 → digraphx-0.5}/setup.cfg +2 -3
  21. {digraphx-0.3 → digraphx-0.5}/src/digraphx/__init__.py +0 -2
  22. digraphx-0.5/src/digraphx/csr_digraph.py +160 -0
  23. digraphx-0.5/src/digraphx/mcf.py +353 -0
  24. {digraphx-0.3 → digraphx-0.5}/src/digraphx/neg_cycle.py +47 -34
  25. {digraphx-0.3 → digraphx-0.5}/src/digraphx/neg_cycle_q.py +56 -39
  26. {digraphx-0.3 → digraphx-0.5}/src/digraphx/tiny_digraph.py +133 -6
  27. {digraphx-0.3 → digraphx-0.5/src/digraphx.egg-info}/PKG-INFO +3 -2
  28. digraphx-0.5/src/digraphx.egg-info/SOURCES.txt +94 -0
  29. {digraphx-0.3 → digraphx-0.5}/src/digraphx.egg-info/requires.txt +2 -1
  30. digraphx-0.5/src/digraphx.egg-info/scm_file_list.json +90 -0
  31. digraphx-0.5/src/digraphx.egg-info/scm_version.json +8 -0
  32. digraphx-0.5/tests/test_coverage_extras.py +51 -0
  33. digraphx-0.5/tests/test_csr_digraph.py +350 -0
  34. {digraphx-0.3 → digraphx-0.5}/tests/test_delay_padding_example_howard.py +28 -15
  35. {digraphx-0.3 → digraphx-0.5}/tests/test_edge_cases.py +81 -20
  36. {digraphx-0.3 → digraphx-0.5}/tests/test_howard_cycle_cancellation.py +2 -2
  37. digraphx-0.5/tests/test_mcf.py +86 -0
  38. digraphx-0.5/tests/test_min_cycle_ratio_extra.py +95 -0
  39. digraphx-0.5/tests/test_min_parametric_q_extra.py +97 -0
  40. {digraphx-0.3 → digraphx-0.5}/tests/test_neg_cycle_q_simple_timing.py +3 -3
  41. {digraphx-0.3 → digraphx-0.5}/tests/test_neg_cycle_simple_timing.py +3 -3
  42. {digraphx-0.3 → digraphx-0.5}/tests/test_stress.py +8 -8
  43. {digraphx-0.3 → digraphx-0.5}/tests/test_timing_example_howard.py +1 -1
  44. digraphx-0.3/CHANGELOG.md +0 -7
  45. digraphx-0.3/IFLOW.md +0 -103
  46. digraphx-0.3/LICENSE +0 -21
  47. digraphx-0.3/cpp_ai/.gitignore +0 -152
  48. digraphx-0.3/cpp_ai/CMakeLists.txt +0 -110
  49. digraphx-0.3/cpp_ai/README.md +0 -185
  50. digraphx-0.3/cpp_ai/examples/basic_usage.cpp +0 -126
  51. digraphx-0.3/cpp_ai/include/cppcoro/async_auto_reset_event.hpp +0 -89
  52. digraphx-0.3/cpp_ai/include/cppcoro/async_generator.hpp +0 -856
  53. digraphx-0.3/cpp_ai/include/cppcoro/async_latch.hpp +0 -62
  54. digraphx-0.3/cpp_ai/include/cppcoro/async_manual_reset_event.hpp +0 -95
  55. digraphx-0.3/cpp_ai/include/cppcoro/async_mutex.hpp +0 -174
  56. digraphx-0.3/cpp_ai/include/cppcoro/async_scope.hpp +0 -81
  57. digraphx-0.3/cpp_ai/include/cppcoro/awaitable_traits.hpp +0 -22
  58. digraphx-0.3/cpp_ai/include/cppcoro/broken_promise.hpp +0 -20
  59. digraphx-0.3/cpp_ai/include/cppcoro/cancellation_registration.hpp +0 -80
  60. digraphx-0.3/cpp_ai/include/cppcoro/cancellation_source.hpp +0 -65
  61. digraphx-0.3/cpp_ai/include/cppcoro/cancellation_token.hpp +0 -63
  62. digraphx-0.3/cpp_ai/include/cppcoro/config.hpp +0 -177
  63. digraphx-0.3/cpp_ai/include/cppcoro/coroutine.hpp +0 -35
  64. digraphx-0.3/cpp_ai/include/cppcoro/detail/any.hpp +0 -17
  65. digraphx-0.3/cpp_ai/include/cppcoro/detail/get_awaiter.hpp +0 -40
  66. digraphx-0.3/cpp_ai/include/cppcoro/detail/is_awaiter.hpp +0 -43
  67. digraphx-0.3/cpp_ai/include/cppcoro/detail/lightweight_manual_reset_event.hpp +0 -56
  68. digraphx-0.3/cpp_ai/include/cppcoro/detail/manual_lifetime.hpp +0 -101
  69. digraphx-0.3/cpp_ai/include/cppcoro/detail/remove_rvalue_reference.hpp +0 -19
  70. digraphx-0.3/cpp_ai/include/cppcoro/detail/sync_wait_task.hpp +0 -226
  71. digraphx-0.3/cpp_ai/include/cppcoro/detail/unwrap_reference.hpp +0 -22
  72. digraphx-0.3/cpp_ai/include/cppcoro/detail/void_value.hpp +0 -14
  73. digraphx-0.3/cpp_ai/include/cppcoro/detail/when_all_counter.hpp +0 -43
  74. digraphx-0.3/cpp_ai/include/cppcoro/detail/when_all_ready_awaitable.hpp +0 -170
  75. digraphx-0.3/cpp_ai/include/cppcoro/detail/when_all_task.hpp +0 -257
  76. digraphx-0.3/cpp_ai/include/cppcoro/detail/win32.hpp +0 -136
  77. digraphx-0.3/cpp_ai/include/cppcoro/detail/win32_overlapped_operation.hpp +0 -295
  78. digraphx-0.3/cpp_ai/include/cppcoro/file.hpp +0 -46
  79. digraphx-0.3/cpp_ai/include/cppcoro/file_buffering_mode.hpp +0 -27
  80. digraphx-0.3/cpp_ai/include/cppcoro/file_open_mode.hpp +0 -38
  81. digraphx-0.3/cpp_ai/include/cppcoro/file_read_operation.hpp +0 -75
  82. digraphx-0.3/cpp_ai/include/cppcoro/file_share_mode.hpp +0 -39
  83. digraphx-0.3/cpp_ai/include/cppcoro/file_write_operation.hpp +0 -74
  84. digraphx-0.3/cpp_ai/include/cppcoro/filesystem.hpp +0 -24
  85. digraphx-0.3/cpp_ai/include/cppcoro/fmap.hpp +0 -131
  86. digraphx-0.3/cpp_ai/include/cppcoro/generator.hpp +0 -187
  87. digraphx-0.3/cpp_ai/include/cppcoro/inline_scheduler.hpp +0 -19
  88. digraphx-0.3/cpp_ai/include/cppcoro/io_service.hpp +0 -277
  89. digraphx-0.3/cpp_ai/include/cppcoro/is_awaitable.hpp +0 -21
  90. digraphx-0.3/cpp_ai/include/cppcoro/multi_producer_sequencer.hpp +0 -721
  91. digraphx-0.3/cpp_ai/include/cppcoro/net/ip_address.hpp +0 -112
  92. digraphx-0.3/cpp_ai/include/cppcoro/net/ip_endpoint.hpp +0 -122
  93. digraphx-0.3/cpp_ai/include/cppcoro/net/ipv4_address.hpp +0 -91
  94. digraphx-0.3/cpp_ai/include/cppcoro/net/ipv4_endpoint.hpp +0 -54
  95. digraphx-0.3/cpp_ai/include/cppcoro/net/ipv6_address.hpp +0 -185
  96. digraphx-0.3/cpp_ai/include/cppcoro/net/ipv6_endpoint.hpp +0 -54
  97. digraphx-0.3/cpp_ai/include/cppcoro/net/socket.hpp +0 -241
  98. digraphx-0.3/cpp_ai/include/cppcoro/net/socket_accept_operation.hpp +0 -86
  99. digraphx-0.3/cpp_ai/include/cppcoro/net/socket_connect_operation.hpp +0 -75
  100. digraphx-0.3/cpp_ai/include/cppcoro/net/socket_disconnect_operation.hpp +0 -70
  101. digraphx-0.3/cpp_ai/include/cppcoro/net/socket_recv_from_operation.hpp +0 -83
  102. digraphx-0.3/cpp_ai/include/cppcoro/net/socket_recv_operation.hpp +0 -70
  103. digraphx-0.3/cpp_ai/include/cppcoro/net/socket_send_operation.hpp +0 -70
  104. digraphx-0.3/cpp_ai/include/cppcoro/net/socket_send_to_operation.hpp +0 -77
  105. digraphx-0.3/cpp_ai/include/cppcoro/on_scope_exit.hpp +0 -111
  106. digraphx-0.3/cpp_ai/include/cppcoro/operation_cancelled.hpp +0 -19
  107. digraphx-0.3/cpp_ai/include/cppcoro/read_only_file.hpp +0 -53
  108. digraphx-0.3/cpp_ai/include/cppcoro/read_write_file.hpp +0 -61
  109. digraphx-0.3/cpp_ai/include/cppcoro/readable_file.hpp +0 -54
  110. digraphx-0.3/cpp_ai/include/cppcoro/recursive_generator.hpp +0 -254
  111. digraphx-0.3/cpp_ai/include/cppcoro/resume_on.hpp +0 -105
  112. digraphx-0.3/cpp_ai/include/cppcoro/round_robin_scheduler.hpp +0 -96
  113. digraphx-0.3/cpp_ai/include/cppcoro/schedule_on.hpp +0 -59
  114. digraphx-0.3/cpp_ai/include/cppcoro/sequence_barrier.hpp +0 -401
  115. digraphx-0.3/cpp_ai/include/cppcoro/sequence_range.hpp +0 -99
  116. digraphx-0.3/cpp_ai/include/cppcoro/sequence_traits.hpp +0 -26
  117. digraphx-0.3/cpp_ai/include/cppcoro/shared_task.hpp +0 -388
  118. digraphx-0.3/cpp_ai/include/cppcoro/single_consumer_async_auto_reset_event.hpp +0 -86
  119. digraphx-0.3/cpp_ai/include/cppcoro/single_consumer_event.hpp +0 -102
  120. digraphx-0.3/cpp_ai/include/cppcoro/single_producer_sequencer.hpp +0 -204
  121. digraphx-0.3/cpp_ai/include/cppcoro/static_thread_pool.hpp +0 -106
  122. digraphx-0.3/cpp_ai/include/cppcoro/sync_wait.hpp +0 -33
  123. digraphx-0.3/cpp_ai/include/cppcoro/task.hpp +0 -374
  124. digraphx-0.3/cpp_ai/include/cppcoro/when_all.hpp +0 -84
  125. digraphx-0.3/cpp_ai/include/cppcoro/when_all_ready.hpp +0 -51
  126. digraphx-0.3/cpp_ai/include/cppcoro/writable_file.hpp +0 -61
  127. digraphx-0.3/cpp_ai/include/cppcoro/write_only_file.hpp +0 -60
  128. digraphx-0.3/cpp_ai/include/digraphx/min_cycle_ratio.hpp +0 -193
  129. digraphx-0.3/cpp_ai/include/digraphx/min_parmetric_q.hpp +0 -164
  130. digraphx-0.3/cpp_ai/include/digraphx/neg_cycle.hpp +0 -245
  131. digraphx-0.3/cpp_ai/include/digraphx/neg_cycle_q.hpp +0 -308
  132. digraphx-0.3/cpp_ai/include/digraphx/parametric.hpp +0 -139
  133. digraphx-0.3/cpp_ai/include/digraphx/tiny_digraph.hpp +0 -364
  134. digraphx-0.3/cpp_ai/include/digraphx/types.hpp +0 -198
  135. digraphx-0.3/cpp_ai/src/min_cycle_ratio.cpp +0 -7
  136. digraphx-0.3/cpp_ai/src/min_parmetric_q.cpp +0 -6
  137. digraphx-0.3/cpp_ai/src/neg_cycle.cpp +0 -4
  138. digraphx-0.3/cpp_ai/src/neg_cycle_q.cpp +0 -4
  139. digraphx-0.3/cpp_ai/src/parametric.cpp +0 -4
  140. digraphx-0.3/cpp_ai/src/tiny_digraph.cpp +0 -4
  141. digraphx-0.3/cpp_ai/tests/integration_tests.cpp +0 -156
  142. digraphx-0.3/cpp_ai/tests/test_neg_cycle.cpp +0 -134
  143. digraphx-0.3/cpp_ai/tests/test_tiny_digraph.cpp +0 -160
  144. digraphx-0.3/cpp_ai/xmake.lua +0 -69
  145. digraphx-0.3/rust_ai/.gitignore +0 -88
  146. digraphx-0.3/rust_ai/Cargo.toml +0 -22
  147. digraphx-0.3/rust_ai/examples/basic_usage.rs +0 -62
  148. digraphx-0.3/rust_ai/src/lib.rs +0 -49
  149. digraphx-0.3/rust_ai/src/min_cycle_ratio.rs +0 -215
  150. digraphx-0.3/rust_ai/src/min_parmetric_q.rs +0 -170
  151. digraphx-0.3/rust_ai/src/neg_cycle.rs +0 -344
  152. digraphx-0.3/rust_ai/src/neg_cycle_q.rs +0 -452
  153. digraphx-0.3/rust_ai/src/parametric.rs +0 -172
  154. digraphx-0.3/rust_ai/src/tiny_digraph.rs +0 -255
  155. digraphx-0.3/rust_ai/tests/integration_tests.rs +0 -49
  156. digraphx-0.3/src/digraphx.egg-info/SOURCES.txt +0 -186
  157. {digraphx-0.3 → digraphx-0.5}/.coveragerc +0 -0
  158. {digraphx-0.3 → digraphx-0.5}/.github/workflows/jekyll-gh-pages.yml +0 -0
  159. {digraphx-0.3 → digraphx-0.5}/.github/workflows/python-publish.yml +0 -0
  160. {digraphx-0.3 → digraphx-0.5}/.isort.cfg +0 -0
  161. {digraphx-0.3 → digraphx-0.5}/.pre-commit-config.yaml +0 -0
  162. {digraphx-0.3 → digraphx-0.5}/.readthedocs.yml +0 -0
  163. {digraphx-0.3 → digraphx-0.5}/.vscode/settings.json +0 -0
  164. {digraphx-0.3 → digraphx-0.5}/AGENTS.md +0 -0
  165. {digraphx-0.3 → digraphx-0.5}/AUTHORS.md +0 -0
  166. {digraphx-0.3 → digraphx-0.5}/CONTRIBUTING.md +0 -0
  167. {digraphx-0.3 → digraphx-0.5}/GEMINI.md +0 -0
  168. {digraphx-0.3 → digraphx-0.5}/LICENSE.txt +0 -0
  169. {digraphx-0.3 → digraphx-0.5}/README.md +0 -0
  170. {digraphx-0.3 → digraphx-0.5}/coverage.json +0 -0
  171. {digraphx-0.3 → digraphx-0.5}/docs/Makefile +0 -0
  172. {digraphx-0.3 → digraphx-0.5}/docs/_static/.gitignore +0 -0
  173. {digraphx-0.3 → digraphx-0.5}/docs/authors.md +0 -0
  174. {digraphx-0.3 → digraphx-0.5}/docs/changelog.md +0 -0
  175. {digraphx-0.3 → digraphx-0.5}/docs/contributing.md +0 -0
  176. {digraphx-0.3 → digraphx-0.5}/docs/license.md +0 -0
  177. {digraphx-0.3 → digraphx-0.5}/docs/readme.md +0 -0
  178. {digraphx-0.3 → digraphx-0.5}/docs/requirements.txt +0 -0
  179. {digraphx-0.3 → digraphx-0.5}/environment.yml +0 -0
  180. {digraphx-0.3 → digraphx-0.5}/experiments/plot_node_colormap.ipynb +0 -0
  181. {digraphx-0.3 → digraphx-0.5}/experiments/plot_node_colormap.py +0 -0
  182. {digraphx-0.3 → digraphx-0.5}/note.md +0 -0
  183. {digraphx-0.3 → digraphx-0.5}/pyproject.toml +0 -0
  184. {digraphx-0.3 → digraphx-0.5}/requirements/README.md +0 -0
  185. {digraphx-0.3 → digraphx-0.5}/requirements/default.txt +0 -0
  186. {digraphx-0.3 → digraphx-0.5}/requirements/doc.txt +0 -0
  187. {digraphx-0.3 → digraphx-0.5}/requirements/test.txt +0 -0
  188. {digraphx-0.3 → digraphx-0.5}/requirements.txt +0 -0
  189. {digraphx-0.3 → digraphx-0.5}/setup.py +0 -0
  190. {digraphx-0.3 → digraphx-0.5}/src/digraphx/min_cycle_ratio.py +0 -0
  191. {digraphx-0.3 → digraphx-0.5}/src/digraphx/min_parametric_q.py +0 -0
  192. {digraphx-0.3 → digraphx-0.5}/src/digraphx/parametric.py +0 -0
  193. {digraphx-0.3 → digraphx-0.5}/src/digraphx/py.typed +0 -0
  194. {digraphx-0.3 → digraphx-0.5}/src/digraphx.egg-info/dependency_links.txt +0 -0
  195. {digraphx-0.3 → digraphx-0.5}/src/digraphx.egg-info/not-zip-safe +0 -0
  196. {digraphx-0.3 → digraphx-0.5}/src/digraphx.egg-info/top_level.txt +0 -0
  197. {digraphx-0.3 → digraphx-0.5}/tests/__init__.py +0 -0
  198. {digraphx-0.3 → digraphx-0.5}/tests/conftest.py +0 -0
  199. {digraphx-0.3 → digraphx-0.5}/tests/test_cycle_ratio.py +0 -0
  200. {digraphx-0.3 → digraphx-0.5}/tests/test_min_cycle_ratio_property.py +0 -0
  201. {digraphx-0.3 → digraphx-0.5}/tests/test_min_parametric_q.py +0 -0
  202. {digraphx-0.3 → digraphx-0.5}/tests/test_neg_cycle.py +0 -0
  203. {digraphx-0.3 → digraphx-0.5}/tests/test_neg_cycle_q.py +0 -0
  204. {digraphx-0.3 → digraphx-0.5}/tests/test_parametric.py +0 -0
  205. {digraphx-0.3 → digraphx-0.5}/tests/test_timing_example.py +0 -0
  206. {digraphx-0.3 → digraphx-0.5}/tests/test_tiny_digraph.py +0 -0
  207. {digraphx-0.3 → digraphx-0.5}/tests/test_tiny_digraph_property.py +0 -0
  208. {digraphx-0.3 → digraphx-0.5}/tox.ini +0 -0
@@ -8,7 +8,7 @@ on:
8
8
  push:
9
9
  # Avoid using all the resources/limits available by checking only
10
10
  # relevant branches and tags. Other branches can be checked via PRs.
11
- branches: [main]
11
+ branches: [ main, dev ]
12
12
  tags: ['v[0-9]*', '[0-9]+.[0-9]+*'] # Match tags that resemble a version
13
13
  pull_request: # Run in every PR
14
14
  workflow_dispatch: # Allow manually triggering the workflow
@@ -60,8 +60,8 @@ jobs:
60
60
  strategy:
61
61
  matrix:
62
62
  python:
63
- - "3.9" # oldest Python supported by PSF
64
- - "3.11" # newest Python that is stable
63
+ - "3.10" # oldest Python supported by PSF
64
+ - "3.13" # newest Python that is stable
65
65
  platform:
66
66
  - ubuntu-latest
67
67
  - macos-latest
@@ -8,7 +8,7 @@ jobs:
8
8
  strategy:
9
9
  matrix:
10
10
  os: ["ubuntu-latest", "windows-latest"]
11
- version: ["3.9", "3.11"]
11
+ version: ["3.10", "3.13"]
12
12
  steps:
13
13
  - uses: actions/checkout@v3
14
14
  - uses: conda-incubator/setup-miniconda@v3
@@ -34,6 +34,5 @@ jobs:
34
34
  conda install pip
35
35
  conda install setuptools wheel setuptools_scm
36
36
  if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
37
- pip install git+https://github.com/luk036/mywheel.git
38
37
  pip install -e .
39
38
  pytest
@@ -5,9 +5,9 @@ name: Python application
5
5
 
6
6
  on:
7
7
  push:
8
- branches: [main]
8
+ branches: [ main, dev ]
9
9
  pull_request:
10
- branches: [main]
10
+ branches: [ main, dev ]
11
11
 
12
12
  jobs:
13
13
  build:
@@ -15,16 +15,15 @@ jobs:
15
15
 
16
16
  steps:
17
17
  - uses: actions/checkout@v2
18
- - name: Set up Python 3.9
18
+ - name: Set up Python 3.10
19
19
  uses: actions/setup-python@v2
20
20
  with:
21
- python-version: 3.9
21
+ python-version: "3.10"
22
22
  - name: Install dependencies
23
23
  run: |
24
24
  python -m pip install --upgrade pip
25
25
  pip install flake8 pytest
26
26
  if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
27
- pip install git+https://github.com/luk036/mywheel.git
28
27
  python setup.py develop
29
28
  # - name: Lint with flake8
30
29
  # run: |
@@ -176,3 +176,7 @@ MANIFEST
176
176
 
177
177
  # my stuffs
178
178
  .hypothesis
179
+
180
+ # Cache dirs
181
+ .ruff_cache/
182
+ .benchmarks/
@@ -0,0 +1,29 @@
1
+ # Changelog
2
+
3
+ ## Version 0.5 (2026-07-16)
4
+
5
+ ### Features
6
+ - **MCF solver**: Added min-cost flow cycle-cancellation solver (`mcf.py`) and `NegCycleFinderQ` with `VertexFilter` for vertex-disjoint MCF constraint. (#582b1eb, #e6e248f)
7
+ - **CSRDiGraph**: Memory-efficient CSR-backed directed graph with `_CSRNeighbors` Mapping view — compact integer arrays instead of dicts. (#12465a3)
8
+ - **Bellman-Ford cycle detection**: Replaced Howard's algorithm with Bellman-Ford for negative cycle detection; fixed parallel edge bug. (#04da700)
9
+ - **spareTSV integration**: Added spare TSV network flow utilities and unified variable naming to `utx`/`vtx` (C++ convention). (#762008a)
10
+ - **Moved spare_tsv to experimental/**: Relocated matplotlib-dependent spare_tsv module out of main package to avoid CI failures when matplotlib is not installed. (#7367e6c)
11
+
12
+ ### Performance
13
+ - **Lazy TinyDiGraph/CSRDiGraph**: Avoid pre-allocating 2N dicts per `init_nodes()` call (~464 MB saved for 1M nodes). (#12465a3)
14
+ - **Incremental residual updates**: Update only edges affected by cycle cancellation instead of rebuilding full residual each iteration. (#12465a3)
15
+
16
+ ### Documentation
17
+ - **plot_directive and svgbob**: Enabled matplotlib plot_directive for auto-generated figures. Added TinyDiGraph and cycle detection example plots. Added svgbob diagram to TinyDiGraph class docstring. (#b64d424)
18
+
19
+ ### Testing & Code Quality
20
+ - **Coverage raised 70%→95%**: Added extensive test coverage including MCF solver, CSRDiGraph, edge cases, and extra min-cycle-ratio tests. (#80da2a2, #9030d8a)
21
+
22
+ ### Code Cleanup
23
+ - **Removed PyScaffold boilerplate**: Deleted `skeleton.py` and `test_skeleton.py`. (#9bdbdb1)
24
+ - **Dropped Python < 3.9 compat**: Removed `importlib-metadata` conditional dependency. (#9bdbdb1)
25
+ - **Config cleanup**: Removed dead entry points, unused mypy ignores, stale `IFLOW.md` and duplicate `LICENSE`. (#9bdbdb1)
26
+
27
+ ### Build & CI
28
+ - **CI repair**: Fixed broken entry_points and remaining skeleton imports. (#91fd90e)
29
+ - **flake8 cleanup**: Removed unused imports and fixed formatting warnings. (#91d7141)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: digraphx
3
- Version: 0.3
3
+ Version: 0.5
4
4
  Summary: Network Optimization Python Code
5
5
  Home-page: https://github.com/luk036/digraphx
6
6
  Author: Wai-Shing Luk
@@ -12,7 +12,8 @@ Classifier: Development Status :: 4 - Beta
12
12
  Classifier: Programming Language :: Python
13
13
  Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
14
14
  License-File: LICENSE.txt
15
- Requires-Dist: importlib-metadata; python_version < "3.9"
15
+ Requires-Dist: importlib-metadata; python_version < "3.10"
16
+ Requires-Dist: mywheel>=0.3
16
17
  Requires-Dist: networkx
17
18
  Provides-Extra: testing
18
19
  Requires-Dist: setuptools; extra == "testing"
@@ -83,6 +83,20 @@ extensions = [
83
83
  "sphinx.ext.mathjax",
84
84
  "sphinx.ext.napoleon",
85
85
  "sphinxcontrib.svgbob",
86
+ "matplotlib.sphinxext.plot_directive",
87
+ ]
88
+
89
+ # -- matplotlib.sphinxext.plot_directive configuration -----------------------
90
+ plot_html_show_source_link = True
91
+ plot_html_show_formats = False
92
+ plot_include_source = True
93
+ plot_working_directory = os.path.join(__location__, "examples")
94
+ plot_pre_code = [
95
+ "import matplotlib.pyplot as plt",
96
+ "import networkx as nx",
97
+ "import numpy as np",
98
+ "from digraphx.tiny_digraph import TinyDiGraph",
99
+ "plt.rcParams['figure.dpi'] = 100",
86
100
  ]
87
101
 
88
102
  # Add any paths that contain templates here, relative to this directory.
@@ -0,0 +1,32 @@
1
+ """
2
+ Negative Cycle Detection
3
+ ========================
4
+
5
+ Demonstrates negative cycle detection in a weighted directed graph.
6
+ """
7
+ import matplotlib.pyplot as plt
8
+ import networkx as nx
9
+
10
+ g = nx.DiGraph()
11
+ g.add_edge(1, 2, w=2)
12
+ g.add_edge(2, 3, w=-3)
13
+ g.add_edge(3, 1, w=1)
14
+ g.add_edge(2, 4, w=1)
15
+ g.add_edge(4, 5, w=-1)
16
+ g.add_edge(5, 2, w=2)
17
+
18
+ plt.figure(figsize=(7, 5))
19
+ pos = nx.spring_layout(g, seed=42)
20
+ nx.draw(
21
+ g,
22
+ pos,
23
+ with_labels=True,
24
+ node_color="lightcoral",
25
+ node_size=600,
26
+ arrowsize=20,
27
+ font_weight="bold",
28
+ )
29
+ edge_labels = {(u, v): f"w={d['w']}" for u, v, d in g.edges(data=True)}
30
+ nx.draw_networkx_edge_labels(g, pos, edge_labels=edge_labels)
31
+ plt.title("Directed Graph with Potential Negative Cycles")
32
+ plt.grid(True, alpha=0.3)
@@ -0,0 +1,33 @@
1
+ """
2
+ TinyDiGraph visualization
3
+ =========================
4
+
5
+ Example of creating and visualizing a directed graph using TinyDiGraph.
6
+ """
7
+ import matplotlib.pyplot as plt
8
+ import networkx as nx
9
+
10
+ from digraphx.tiny_digraph import TinyDiGraph
11
+
12
+ g = TinyDiGraph()
13
+ g.init_nodes(5)
14
+ g.add_edge(0, 1)
15
+ g.add_edge(1, 2)
16
+ g.add_edge(2, 3)
17
+ g.add_edge(3, 0)
18
+ g.add_edge(0, 4)
19
+ g.add_edge(4, 2)
20
+
21
+ plt.figure(figsize=(6, 5))
22
+ pos = nx.spring_layout(g, seed=42)
23
+ nx.draw(
24
+ g,
25
+ pos,
26
+ with_labels=True,
27
+ node_color="lightblue",
28
+ node_size=500,
29
+ arrowsize=20,
30
+ font_weight="bold",
31
+ )
32
+ plt.title("TinyDiGraph with 5 nodes and 6 edges")
33
+ plt.grid(True, alpha=0.3)
@@ -0,0 +1,25 @@
1
+ # Figures Demo
2
+
3
+ Auto-generated figures demonstrating digraphx functionality.
4
+
5
+ ## TinyDiGraph Example
6
+
7
+ ```{plot} examples/plot_tiny_digraph.py
8
+ ```
9
+
10
+ ## Cycle Detection Example
11
+
12
+ ```{plot} examples/plot_cycle_detection.py
13
+ ```
14
+
15
+ ### Inline Plot Example
16
+
17
+ ```{plot}
18
+ import matplotlib.pyplot as plt
19
+ import numpy as np
20
+
21
+ x = np.linspace(0, 2 * np.pi, 100)
22
+ plt.plot(x, np.sin(x))
23
+ plt.title("Simple Sine Wave")
24
+ plt.grid(True, alpha=0.3)
25
+ ```
@@ -22,6 +22,7 @@ Contributions & Help <contributing>
22
22
  License <license>
23
23
  Authors <authors>
24
24
  Changelog <changelog>
25
+ Figures Demo <figures_demo>
25
26
  Module Reference <api/modules>
26
27
  ```
27
28
 
@@ -0,0 +1,5 @@
1
+ """Experimental / plotting utilities for digraphx.
2
+
3
+ This module contains experimental code including the spare TSV
4
+ network flow visualization utilities (requires matplotlib).
5
+ """
@@ -0,0 +1,190 @@
1
+ """Spare TSV network optimization utilities."""
2
+
3
+ import math
4
+
5
+ import matplotlib.pyplot as plt
6
+ import networkx as nx
7
+ import numpy as np
8
+
9
+
10
+ def vdc(n, base=2):
11
+ """Van der Corput sequence."""
12
+ vdc, denom = 0.0, 1.0
13
+ while n:
14
+ denom *= base
15
+ n, remainder = divmod(n, base)
16
+ vdc += remainder / denom
17
+ return vdc
18
+
19
+
20
+ def vdcorput(n, base=2):
21
+ """Generate n Van der Corput vectors as a list."""
22
+ return [vdc(i, base) for i in range(n)]
23
+
24
+
25
+ def vdcorput_iter(n, base=2):
26
+ """Generate n Van der Corput vectors lazily (generator).
27
+
28
+ Yields values one at a time instead of allocating a full list.
29
+ """
30
+ for i in range(n):
31
+ yield vdc(i, base)
32
+
33
+
34
+ def formGraph(T, pos, mu, eta, seed=None):
35
+ """Form N by N grid of nodes, perturb by mu and connect nodes within eta.
36
+
37
+ mu and eta are relative to 1/(N-1).
38
+ """
39
+ if seed is not None:
40
+ np.random.seed(seed)
41
+
42
+ N = int(np.sqrt(T))
43
+ mu = mu / (N - 1)
44
+ eta = eta / (N - 1)
45
+
46
+ pos = dict(enumerate(pos))
47
+ n = len(pos)
48
+
49
+ gra = nx.random_geometric_graph(n, eta, pos=pos)
50
+ gra = nx.DiGraph(gra)
51
+ return gra
52
+
53
+
54
+ def showPaths(gra, pos, N, edgeProbs=1.0, path=None, visibleNodes=None, guards=None):
55
+ """Draw directed graph with optional path overlay.
56
+
57
+ Parameters
58
+ ----------
59
+ gra : nx.DiGraph
60
+ Directed graph.
61
+ pos : dict
62
+ Node positions {node: (x, y)}.
63
+ N : int
64
+ Number of primal nodes (drawn cyan).
65
+ edgeProbs : float or list
66
+ Edge probability/weights for coloring.
67
+ path : list of edges
68
+ Edges to highlight in blue.
69
+ visibleNodes : list
70
+ Nodes to draw (default: all).
71
+ guards : list
72
+ Nodes to highlight with black dots.
73
+ """
74
+ fig = plt.figure(figsize=(8, 6))
75
+ ax = fig.add_subplot(111, aspect="equal")
76
+
77
+ n = gra.number_of_nodes()
78
+ if visibleNodes is None:
79
+ visibleNodes = gra.nodes()
80
+ primalNodes = range(0, N)
81
+ spareNodes = range(N, n)
82
+
83
+ nx.draw_networkx_nodes(
84
+ gra, pos, nodelist=primalNodes, node_color="c", node_size=50, ax=ax
85
+ )
86
+ nx.draw_networkx_nodes(
87
+ gra, pos, nodelist=spareNodes, node_color="r", node_size=50, ax=ax
88
+ )
89
+
90
+ if guards is not None:
91
+ nx.draw_networkx_nodes(
92
+ gra, pos, nodelist=guards, node_color=".0", node_size=100, ax=ax
93
+ )
94
+
95
+ alpha = 1.0 if path is None else 0.15
96
+
97
+ # only display edges between non-dummy nodes
98
+ edge_list = list(gra.edges())
99
+ visibleEdges = [
100
+ e
101
+ for e in edge_list
102
+ if e[0] in visibleNodes and e[1] in visibleNodes
103
+ ]
104
+
105
+ if isinstance(edgeProbs, float):
106
+ edgeProbs = [edgeProbs] * gra.number_of_edges()
107
+ p = [edgeProbs[i] for i, e in enumerate(edge_list) if e in visibleEdges]
108
+
109
+ nx.draw_networkx_edges(
110
+ gra,
111
+ pos,
112
+ edge_color=p,
113
+ width=1,
114
+ edge_cmap=plt.cm.RdYlGn,
115
+ arrows=False,
116
+ edgelist=visibleEdges,
117
+ edge_vmin=0.0,
118
+ edge_vmax=1.0,
119
+ ax=ax,
120
+ alpha=alpha,
121
+ )
122
+
123
+ if path is not None:
124
+ nx.draw_networkx_edges(
125
+ gra,
126
+ pos,
127
+ edge_color="b",
128
+ width=1,
129
+ edge_cmap=plt.cm.RdYlGn,
130
+ edgelist=path,
131
+ arrows=True,
132
+ edge_vmin=0.0,
133
+ edge_vmax=1.0,
134
+ )
135
+
136
+ ax.axis([-0.05, 1.05, -0.05, 1.05])
137
+ ax.axis("off")
138
+
139
+ return fig, ax
140
+
141
+
142
+ def setup_network_flow(gra, pos, primal_count, capacity):
143
+ """Configure graph for spare TSV network flow.
144
+
145
+ Sets edge weights (Euclidean distance * 100), assigns demand=-1 to
146
+ primal nodes and demand=0 to spare nodes, then adds a sink node
147
+ connected to all spare nodes.
148
+
149
+ Returns the sink node index.
150
+ """
151
+ total = gra.number_of_nodes()
152
+ for u, v in gra.edges():
153
+ dx = pos[u][0] - pos[v][0]
154
+ dy = pos[u][1] - pos[v][1]
155
+ gra[u][v]["weight"] = int(math.sqrt(dx * dx + dy * dy) * 100)
156
+ gra[u][v]["capacity"] = capacity
157
+
158
+ for i in range(primal_count):
159
+ gra.nodes[i]["demand"] = -1
160
+ for i in range(primal_count, total):
161
+ gra.nodes[i]["demand"] = 0
162
+
163
+ sink = total
164
+ gra.add_node(sink, demand=primal_count)
165
+ for i in range(primal_count, total):
166
+ gra.add_edge(i, sink, capacity=capacity)
167
+
168
+ return sink
169
+
170
+
171
+ def solve_network_flow(gra, sink_node):
172
+ """Solve network flow and return path edges.
173
+
174
+ Returns (flow_cost, path_list) where path_list contains edges
175
+ with positive flow (excluding edges to the sink).
176
+
177
+ Returns None if the problem is infeasible.
178
+ """
179
+ try:
180
+ flow_cost, flow_dict = nx.network_simplex(gra)
181
+ except nx.NetworkXUnfeasible:
182
+ return None
183
+
184
+ path_list = [
185
+ (u, v)
186
+ for u in flow_dict
187
+ for v in flow_dict[u]
188
+ if flow_dict[u][v] > 0 and v != sink_node
189
+ ]
190
+ return flow_cost, path_list
@@ -0,0 +1,170 @@
1
+ """Tests for spareTSV (experimental)."""
2
+
3
+ import networkx as nx
4
+ import pytest
5
+
6
+ from experimental.spare_tsv import (
7
+ formGraph,
8
+ setup_network_flow,
9
+ showPaths,
10
+ solve_network_flow,
11
+ vdc,
12
+ vdcorput,
13
+ vdcorput_iter,
14
+ )
15
+
16
+
17
+ def _vdc(n, base=2):
18
+ v, denom = 0.0, 1.0
19
+ while n:
20
+ denom *= base
21
+ n, remainder = divmod(n, base)
22
+ v += remainder / denom
23
+ return v
24
+
25
+
26
+ @pytest.fixture
27
+ def sample_positions():
28
+ """Van der Corput positions (T=12, bases 2/3)."""
29
+ T = 12
30
+ xbase, ybase = 2, 3
31
+ x = [_vdc(i, xbase) for i in range(T)]
32
+ y = [_vdc(i, ybase) for i in range(T)]
33
+ return list(zip(x, y))
34
+
35
+
36
+ @pytest.fixture
37
+ def small_graph(sample_positions):
38
+ """Small geometric graph for spareTSV tests."""
39
+ return formGraph(12, sample_positions, 0.12, 1.6, seed=5)
40
+
41
+
42
+ def test_vdc():
43
+ result = vdc(1, base=2)
44
+ assert result == 0.5
45
+
46
+
47
+ def test_vdc_sequence_base_2():
48
+ vals = [vdc(i, base=2) for i in range(4)]
49
+ assert vals == [0.0, 0.5, 0.25, 0.75]
50
+
51
+
52
+ def test_vdc_sequence_base_3():
53
+ vals = [vdc(i, base=3) for i in range(3)]
54
+ assert vals == [0.0, 1 / 3, 2 / 3]
55
+
56
+
57
+ def test_vdcorput():
58
+ result = vdcorput(5, base=2)
59
+ assert len(result) == 5
60
+ assert all(0 <= x < 1 for x in result)
61
+
62
+
63
+ def test_vdcorput_iter():
64
+ result = list(vdcorput_iter(5, base=2))
65
+ assert len(result) == 5
66
+ assert result == vdcorput(5, base=2)
67
+
68
+
69
+ def test_formGraph(sample_positions):
70
+ gra = formGraph(12, sample_positions, 0.12, 1.6, seed=5)
71
+ assert gra.number_of_nodes() == 12
72
+ assert gra.is_directed()
73
+
74
+
75
+ def test_formGraph_reproducible(sample_positions):
76
+ g1 = formGraph(12, sample_positions, 0.12, 1.6, seed=42)
77
+ g2 = formGraph(12, sample_positions, 0.12, 1.6, seed=42)
78
+ assert g1.number_of_edges() == g2.number_of_edges()
79
+
80
+
81
+ def test_formGraph_no_seed(sample_positions):
82
+ gra = formGraph(12, sample_positions, 0.12, 1.6)
83
+ assert gra.number_of_nodes() == 12
84
+ assert gra.is_directed()
85
+
86
+
87
+ def test_showPaths(small_graph, sample_positions):
88
+ N = 9
89
+ pos = dict(enumerate(sample_positions))
90
+ fig, ax = showPaths(small_graph, pos, N)
91
+ assert fig is not None
92
+ assert ax is not None
93
+
94
+
95
+ def test_showPaths_with_options(small_graph, sample_positions):
96
+ N = 9
97
+ pos = dict(enumerate(sample_positions))
98
+ edges = list(small_graph.edges())[:3]
99
+ edge_probs = [0.5] * small_graph.number_of_edges()
100
+ vis = list(range(9))
101
+ fig, ax = showPaths(
102
+ small_graph,
103
+ pos,
104
+ N,
105
+ edgeProbs=edge_probs,
106
+ path=edges,
107
+ visibleNodes=vis,
108
+ guards=[0, 1],
109
+ )
110
+ assert fig is not None
111
+ assert ax is not None
112
+
113
+
114
+ def test_setup_network_flow(small_graph, sample_positions):
115
+ gra = small_graph.copy()
116
+ total = gra.number_of_nodes()
117
+ sink = setup_network_flow(gra, sample_positions, primal_count=9, capacity=4)
118
+
119
+ assert sink == total
120
+ assert gra.number_of_nodes() == total + 1
121
+ assert gra.has_node(sink)
122
+ assert gra.nodes[0]["demand"] == -1
123
+ assert gra.nodes[9]["demand"] == 0
124
+ assert gra.nodes[sink]["demand"] == 9
125
+
126
+
127
+ def test_setup_network_flow_edge_attrs(small_graph, sample_positions):
128
+ gra = small_graph.copy()
129
+ setup_network_flow(gra, sample_positions, primal_count=9, capacity=4)
130
+
131
+ u, v = next(iter(gra.edges()))
132
+ assert "weight" in gra[u][v]
133
+ assert "capacity" in gra[u][v]
134
+ assert gra[u][v]["capacity"] == 4
135
+ assert isinstance(gra[u][v]["weight"], int)
136
+
137
+
138
+ def test_solve_network_flow(small_graph, sample_positions):
139
+ gra = small_graph.copy()
140
+ sink = setup_network_flow(gra, sample_positions, primal_count=9, capacity=4)
141
+ result = solve_network_flow(gra, sink)
142
+
143
+ assert result is not None
144
+ flow_cost, path_list = result
145
+ assert isinstance(path_list, list)
146
+ assert flow_cost >= 0
147
+
148
+
149
+ def test_solve_network_flow_path_excludes_sink(small_graph, sample_positions):
150
+ gra = small_graph.copy()
151
+ sink = setup_network_flow(gra, sample_positions, primal_count=9, capacity=4)
152
+ result = solve_network_flow(gra, sink)
153
+
154
+ assert result is not None
155
+ _, path_list = result
156
+ for u, v in path_list:
157
+ assert v != sink
158
+
159
+
160
+ def test_solve_network_flow_infeasible():
161
+ gra = nx.DiGraph()
162
+ gra.add_node(0, demand=-1)
163
+ gra.add_node(1, demand=0)
164
+ gra.add_edge(0, 1, weight=1, capacity=0)
165
+ sink = 2
166
+ gra.add_node(sink, demand=1)
167
+ gra.add_edge(1, sink, capacity=0)
168
+
169
+ result = solve_network_flow(gra, sink)
170
+ assert result is None
@@ -0,0 +1,79 @@
1
+ # Experiment: spare TSV network flow optimization using cycle-cancellation descent
2
+
3
+ import matplotlib
4
+
5
+ matplotlib.use("Agg")
6
+ import matplotlib.pyplot as plt # noqa: E402
7
+
8
+ from digraphx.mcf import cycle_canceling_mcf # noqa: E402
9
+ from experimental.spare_tsv import ( # noqa: E402
10
+ formGraph,
11
+ setup_network_flow,
12
+ showPaths,
13
+ vdcorput,
14
+ )
15
+
16
+
17
+ def nx_to_dict_graph(gra, sink):
18
+ """Convert networkx graph to dict-of-dicts for cycle_canceling_mcf.
19
+
20
+ Extracts weight/capacity from edge data and demand from node data.
21
+ Excludes the sink node (added separately by setup logic).
22
+ """
23
+ g = {}
24
+ demands = {}
25
+ for u in gra.nodes():
26
+ d = gra.nodes[u].get("demand", 0)
27
+ if d != 0 or u == sink:
28
+ demands[u] = d
29
+ for u, v, data in gra.edges(data=True):
30
+ if u not in g:
31
+ g[u] = {}
32
+ g[u][v] = {
33
+ "weight": data.get("weight", 0),
34
+ "capacity": data.get("capacity", 1),
35
+ }
36
+ return g, demands
37
+
38
+
39
+ N = 155
40
+ M = 40
41
+ r = 4
42
+
43
+ T = N + M
44
+ xbase = 2
45
+ ybase = 3
46
+ x = [i for i in vdcorput(T, xbase)]
47
+ y = [i for i in vdcorput(T, ybase)]
48
+ pos = list(zip(x, y))
49
+ gra = formGraph(T, pos, 0.12, 1.6, seed=5)
50
+ pos2 = dict(enumerate(pos))
51
+ fig, ax = showPaths(gra, pos2, N)
52
+ fig.savefig("spareTSV-initial.svg")
53
+ plt.close(fig)
54
+
55
+ # Set up the network flow graph (weights, capacities, demands, sink)
56
+ sink = setup_network_flow(gra, pos, primal_count=N, capacity=r)
57
+
58
+ # Convert to dict-of-dicts format and solve via cycle-cancellation descent
59
+ g, demands = nx_to_dict_graph(gra, sink)
60
+ result = cycle_canceling_mcf(g, demands, sink=sink)
61
+
62
+ if result is None:
63
+ print("Solution Infeasible!")
64
+ else:
65
+ flow_cost, flow_dict = result
66
+ print(f"Flow cost: {flow_cost}")
67
+
68
+ # Extract path edges with positive flow, excluding sink
69
+ pathlist = [
70
+ (u, v)
71
+ for u in flow_dict
72
+ for v, f in flow_dict[u].items()
73
+ if f > 0 and v != sink
74
+ ]
75
+
76
+ gra.remove_node(sink)
77
+ fig, ax = showPaths(gra, pos2, N, path=pathlist)
78
+ fig.savefig("spareTSV-solution.svg")
79
+ plt.close(fig)