digraphx 0.3__tar.gz → 0.4__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 (188) hide show
  1. {digraphx-0.3 → digraphx-0.4}/.github/workflows/ci.bak +1 -1
  2. {digraphx-0.3 → digraphx-0.4}/.github/workflows/python-app.yml +2 -2
  3. {digraphx-0.3/src/digraphx.egg-info → digraphx-0.4}/PKG-INFO +1 -1
  4. {digraphx-0.3 → digraphx-0.4}/src/digraphx/neg_cycle.py +42 -11
  5. {digraphx-0.3 → digraphx-0.4}/src/digraphx/neg_cycle_q.py +38 -14
  6. {digraphx-0.3 → digraphx-0.4/src/digraphx.egg-info}/PKG-INFO +1 -1
  7. digraphx-0.4/src/digraphx.egg-info/SOURCES.txt +77 -0
  8. {digraphx-0.3 → digraphx-0.4}/tests/test_delay_padding_example_howard.py +9 -2
  9. {digraphx-0.3 → digraphx-0.4}/tests/test_edge_cases.py +81 -20
  10. {digraphx-0.3 → digraphx-0.4}/tests/test_howard_cycle_cancellation.py +2 -2
  11. {digraphx-0.3 → digraphx-0.4}/tests/test_neg_cycle_q_simple_timing.py +3 -3
  12. {digraphx-0.3 → digraphx-0.4}/tests/test_neg_cycle_simple_timing.py +3 -3
  13. {digraphx-0.3 → digraphx-0.4}/tests/test_stress.py +8 -8
  14. {digraphx-0.3 → digraphx-0.4}/tests/test_timing_example_howard.py +1 -1
  15. digraphx-0.3/cpp_ai/.gitignore +0 -152
  16. digraphx-0.3/cpp_ai/CMakeLists.txt +0 -110
  17. digraphx-0.3/cpp_ai/README.md +0 -185
  18. digraphx-0.3/cpp_ai/examples/basic_usage.cpp +0 -126
  19. digraphx-0.3/cpp_ai/include/cppcoro/async_auto_reset_event.hpp +0 -89
  20. digraphx-0.3/cpp_ai/include/cppcoro/async_generator.hpp +0 -856
  21. digraphx-0.3/cpp_ai/include/cppcoro/async_latch.hpp +0 -62
  22. digraphx-0.3/cpp_ai/include/cppcoro/async_manual_reset_event.hpp +0 -95
  23. digraphx-0.3/cpp_ai/include/cppcoro/async_mutex.hpp +0 -174
  24. digraphx-0.3/cpp_ai/include/cppcoro/async_scope.hpp +0 -81
  25. digraphx-0.3/cpp_ai/include/cppcoro/awaitable_traits.hpp +0 -22
  26. digraphx-0.3/cpp_ai/include/cppcoro/broken_promise.hpp +0 -20
  27. digraphx-0.3/cpp_ai/include/cppcoro/cancellation_registration.hpp +0 -80
  28. digraphx-0.3/cpp_ai/include/cppcoro/cancellation_source.hpp +0 -65
  29. digraphx-0.3/cpp_ai/include/cppcoro/cancellation_token.hpp +0 -63
  30. digraphx-0.3/cpp_ai/include/cppcoro/config.hpp +0 -177
  31. digraphx-0.3/cpp_ai/include/cppcoro/coroutine.hpp +0 -35
  32. digraphx-0.3/cpp_ai/include/cppcoro/detail/any.hpp +0 -17
  33. digraphx-0.3/cpp_ai/include/cppcoro/detail/get_awaiter.hpp +0 -40
  34. digraphx-0.3/cpp_ai/include/cppcoro/detail/is_awaiter.hpp +0 -43
  35. digraphx-0.3/cpp_ai/include/cppcoro/detail/lightweight_manual_reset_event.hpp +0 -56
  36. digraphx-0.3/cpp_ai/include/cppcoro/detail/manual_lifetime.hpp +0 -101
  37. digraphx-0.3/cpp_ai/include/cppcoro/detail/remove_rvalue_reference.hpp +0 -19
  38. digraphx-0.3/cpp_ai/include/cppcoro/detail/sync_wait_task.hpp +0 -226
  39. digraphx-0.3/cpp_ai/include/cppcoro/detail/unwrap_reference.hpp +0 -22
  40. digraphx-0.3/cpp_ai/include/cppcoro/detail/void_value.hpp +0 -14
  41. digraphx-0.3/cpp_ai/include/cppcoro/detail/when_all_counter.hpp +0 -43
  42. digraphx-0.3/cpp_ai/include/cppcoro/detail/when_all_ready_awaitable.hpp +0 -170
  43. digraphx-0.3/cpp_ai/include/cppcoro/detail/when_all_task.hpp +0 -257
  44. digraphx-0.3/cpp_ai/include/cppcoro/detail/win32.hpp +0 -136
  45. digraphx-0.3/cpp_ai/include/cppcoro/detail/win32_overlapped_operation.hpp +0 -295
  46. digraphx-0.3/cpp_ai/include/cppcoro/file.hpp +0 -46
  47. digraphx-0.3/cpp_ai/include/cppcoro/file_buffering_mode.hpp +0 -27
  48. digraphx-0.3/cpp_ai/include/cppcoro/file_open_mode.hpp +0 -38
  49. digraphx-0.3/cpp_ai/include/cppcoro/file_read_operation.hpp +0 -75
  50. digraphx-0.3/cpp_ai/include/cppcoro/file_share_mode.hpp +0 -39
  51. digraphx-0.3/cpp_ai/include/cppcoro/file_write_operation.hpp +0 -74
  52. digraphx-0.3/cpp_ai/include/cppcoro/filesystem.hpp +0 -24
  53. digraphx-0.3/cpp_ai/include/cppcoro/fmap.hpp +0 -131
  54. digraphx-0.3/cpp_ai/include/cppcoro/generator.hpp +0 -187
  55. digraphx-0.3/cpp_ai/include/cppcoro/inline_scheduler.hpp +0 -19
  56. digraphx-0.3/cpp_ai/include/cppcoro/io_service.hpp +0 -277
  57. digraphx-0.3/cpp_ai/include/cppcoro/is_awaitable.hpp +0 -21
  58. digraphx-0.3/cpp_ai/include/cppcoro/multi_producer_sequencer.hpp +0 -721
  59. digraphx-0.3/cpp_ai/include/cppcoro/net/ip_address.hpp +0 -112
  60. digraphx-0.3/cpp_ai/include/cppcoro/net/ip_endpoint.hpp +0 -122
  61. digraphx-0.3/cpp_ai/include/cppcoro/net/ipv4_address.hpp +0 -91
  62. digraphx-0.3/cpp_ai/include/cppcoro/net/ipv4_endpoint.hpp +0 -54
  63. digraphx-0.3/cpp_ai/include/cppcoro/net/ipv6_address.hpp +0 -185
  64. digraphx-0.3/cpp_ai/include/cppcoro/net/ipv6_endpoint.hpp +0 -54
  65. digraphx-0.3/cpp_ai/include/cppcoro/net/socket.hpp +0 -241
  66. digraphx-0.3/cpp_ai/include/cppcoro/net/socket_accept_operation.hpp +0 -86
  67. digraphx-0.3/cpp_ai/include/cppcoro/net/socket_connect_operation.hpp +0 -75
  68. digraphx-0.3/cpp_ai/include/cppcoro/net/socket_disconnect_operation.hpp +0 -70
  69. digraphx-0.3/cpp_ai/include/cppcoro/net/socket_recv_from_operation.hpp +0 -83
  70. digraphx-0.3/cpp_ai/include/cppcoro/net/socket_recv_operation.hpp +0 -70
  71. digraphx-0.3/cpp_ai/include/cppcoro/net/socket_send_operation.hpp +0 -70
  72. digraphx-0.3/cpp_ai/include/cppcoro/net/socket_send_to_operation.hpp +0 -77
  73. digraphx-0.3/cpp_ai/include/cppcoro/on_scope_exit.hpp +0 -111
  74. digraphx-0.3/cpp_ai/include/cppcoro/operation_cancelled.hpp +0 -19
  75. digraphx-0.3/cpp_ai/include/cppcoro/read_only_file.hpp +0 -53
  76. digraphx-0.3/cpp_ai/include/cppcoro/read_write_file.hpp +0 -61
  77. digraphx-0.3/cpp_ai/include/cppcoro/readable_file.hpp +0 -54
  78. digraphx-0.3/cpp_ai/include/cppcoro/recursive_generator.hpp +0 -254
  79. digraphx-0.3/cpp_ai/include/cppcoro/resume_on.hpp +0 -105
  80. digraphx-0.3/cpp_ai/include/cppcoro/round_robin_scheduler.hpp +0 -96
  81. digraphx-0.3/cpp_ai/include/cppcoro/schedule_on.hpp +0 -59
  82. digraphx-0.3/cpp_ai/include/cppcoro/sequence_barrier.hpp +0 -401
  83. digraphx-0.3/cpp_ai/include/cppcoro/sequence_range.hpp +0 -99
  84. digraphx-0.3/cpp_ai/include/cppcoro/sequence_traits.hpp +0 -26
  85. digraphx-0.3/cpp_ai/include/cppcoro/shared_task.hpp +0 -388
  86. digraphx-0.3/cpp_ai/include/cppcoro/single_consumer_async_auto_reset_event.hpp +0 -86
  87. digraphx-0.3/cpp_ai/include/cppcoro/single_consumer_event.hpp +0 -102
  88. digraphx-0.3/cpp_ai/include/cppcoro/single_producer_sequencer.hpp +0 -204
  89. digraphx-0.3/cpp_ai/include/cppcoro/static_thread_pool.hpp +0 -106
  90. digraphx-0.3/cpp_ai/include/cppcoro/sync_wait.hpp +0 -33
  91. digraphx-0.3/cpp_ai/include/cppcoro/task.hpp +0 -374
  92. digraphx-0.3/cpp_ai/include/cppcoro/when_all.hpp +0 -84
  93. digraphx-0.3/cpp_ai/include/cppcoro/when_all_ready.hpp +0 -51
  94. digraphx-0.3/cpp_ai/include/cppcoro/writable_file.hpp +0 -61
  95. digraphx-0.3/cpp_ai/include/cppcoro/write_only_file.hpp +0 -60
  96. digraphx-0.3/cpp_ai/include/digraphx/min_cycle_ratio.hpp +0 -193
  97. digraphx-0.3/cpp_ai/include/digraphx/min_parmetric_q.hpp +0 -164
  98. digraphx-0.3/cpp_ai/include/digraphx/neg_cycle.hpp +0 -245
  99. digraphx-0.3/cpp_ai/include/digraphx/neg_cycle_q.hpp +0 -308
  100. digraphx-0.3/cpp_ai/include/digraphx/parametric.hpp +0 -139
  101. digraphx-0.3/cpp_ai/include/digraphx/tiny_digraph.hpp +0 -364
  102. digraphx-0.3/cpp_ai/include/digraphx/types.hpp +0 -198
  103. digraphx-0.3/cpp_ai/src/min_cycle_ratio.cpp +0 -7
  104. digraphx-0.3/cpp_ai/src/min_parmetric_q.cpp +0 -6
  105. digraphx-0.3/cpp_ai/src/neg_cycle.cpp +0 -4
  106. digraphx-0.3/cpp_ai/src/neg_cycle_q.cpp +0 -4
  107. digraphx-0.3/cpp_ai/src/parametric.cpp +0 -4
  108. digraphx-0.3/cpp_ai/src/tiny_digraph.cpp +0 -4
  109. digraphx-0.3/cpp_ai/tests/integration_tests.cpp +0 -156
  110. digraphx-0.3/cpp_ai/tests/test_neg_cycle.cpp +0 -134
  111. digraphx-0.3/cpp_ai/tests/test_tiny_digraph.cpp +0 -160
  112. digraphx-0.3/cpp_ai/xmake.lua +0 -69
  113. digraphx-0.3/rust_ai/.gitignore +0 -88
  114. digraphx-0.3/rust_ai/Cargo.toml +0 -22
  115. digraphx-0.3/rust_ai/examples/basic_usage.rs +0 -62
  116. digraphx-0.3/rust_ai/src/lib.rs +0 -49
  117. digraphx-0.3/rust_ai/src/min_cycle_ratio.rs +0 -215
  118. digraphx-0.3/rust_ai/src/min_parmetric_q.rs +0 -170
  119. digraphx-0.3/rust_ai/src/neg_cycle.rs +0 -344
  120. digraphx-0.3/rust_ai/src/neg_cycle_q.rs +0 -452
  121. digraphx-0.3/rust_ai/src/parametric.rs +0 -172
  122. digraphx-0.3/rust_ai/src/tiny_digraph.rs +0 -255
  123. digraphx-0.3/rust_ai/tests/integration_tests.rs +0 -49
  124. digraphx-0.3/src/digraphx.egg-info/SOURCES.txt +0 -186
  125. {digraphx-0.3 → digraphx-0.4}/.coveragerc +0 -0
  126. {digraphx-0.3 → digraphx-0.4}/.github/workflows/jekyll-gh-pages.yml +0 -0
  127. {digraphx-0.3 → digraphx-0.4}/.github/workflows/multi-platforms.yml +0 -0
  128. {digraphx-0.3 → digraphx-0.4}/.github/workflows/python-publish.yml +0 -0
  129. {digraphx-0.3 → digraphx-0.4}/.gitignore +0 -0
  130. {digraphx-0.3 → digraphx-0.4}/.isort.cfg +0 -0
  131. {digraphx-0.3 → digraphx-0.4}/.pre-commit-config.yaml +0 -0
  132. {digraphx-0.3 → digraphx-0.4}/.readthedocs.yml +0 -0
  133. {digraphx-0.3 → digraphx-0.4}/.vscode/settings.json +0 -0
  134. {digraphx-0.3 → digraphx-0.4}/AGENTS.md +0 -0
  135. {digraphx-0.3 → digraphx-0.4}/AUTHORS.md +0 -0
  136. {digraphx-0.3 → digraphx-0.4}/CHANGELOG.md +0 -0
  137. {digraphx-0.3 → digraphx-0.4}/CONTRIBUTING.md +0 -0
  138. {digraphx-0.3 → digraphx-0.4}/GEMINI.md +0 -0
  139. {digraphx-0.3 → digraphx-0.4}/IFLOW.md +0 -0
  140. {digraphx-0.3 → digraphx-0.4}/LICENSE +0 -0
  141. {digraphx-0.3 → digraphx-0.4}/LICENSE.txt +0 -0
  142. {digraphx-0.3 → digraphx-0.4}/README.md +0 -0
  143. {digraphx-0.3 → digraphx-0.4}/coverage.json +0 -0
  144. {digraphx-0.3 → digraphx-0.4}/docs/Makefile +0 -0
  145. {digraphx-0.3 → digraphx-0.4}/docs/_static/.gitignore +0 -0
  146. {digraphx-0.3 → digraphx-0.4}/docs/authors.md +0 -0
  147. {digraphx-0.3 → digraphx-0.4}/docs/changelog.md +0 -0
  148. {digraphx-0.3 → digraphx-0.4}/docs/conf.py +0 -0
  149. {digraphx-0.3 → digraphx-0.4}/docs/contributing.md +0 -0
  150. {digraphx-0.3 → digraphx-0.4}/docs/index.md +0 -0
  151. {digraphx-0.3 → digraphx-0.4}/docs/license.md +0 -0
  152. {digraphx-0.3 → digraphx-0.4}/docs/readme.md +0 -0
  153. {digraphx-0.3 → digraphx-0.4}/docs/requirements.txt +0 -0
  154. {digraphx-0.3 → digraphx-0.4}/environment.yml +0 -0
  155. {digraphx-0.3 → digraphx-0.4}/experiments/plot_node_colormap.ipynb +0 -0
  156. {digraphx-0.3 → digraphx-0.4}/experiments/plot_node_colormap.py +0 -0
  157. {digraphx-0.3 → digraphx-0.4}/mypy.ini +0 -0
  158. {digraphx-0.3 → digraphx-0.4}/note.md +0 -0
  159. {digraphx-0.3 → digraphx-0.4}/pyproject.toml +0 -0
  160. {digraphx-0.3 → digraphx-0.4}/requirements/README.md +0 -0
  161. {digraphx-0.3 → digraphx-0.4}/requirements/default.txt +0 -0
  162. {digraphx-0.3 → digraphx-0.4}/requirements/doc.txt +0 -0
  163. {digraphx-0.3 → digraphx-0.4}/requirements/test.txt +0 -0
  164. {digraphx-0.3 → digraphx-0.4}/requirements.txt +0 -0
  165. {digraphx-0.3 → digraphx-0.4}/setup.cfg +0 -0
  166. {digraphx-0.3 → digraphx-0.4}/setup.py +0 -0
  167. {digraphx-0.3 → digraphx-0.4}/src/digraphx/__init__.py +0 -0
  168. {digraphx-0.3 → digraphx-0.4}/src/digraphx/min_cycle_ratio.py +0 -0
  169. {digraphx-0.3 → digraphx-0.4}/src/digraphx/min_parametric_q.py +0 -0
  170. {digraphx-0.3 → digraphx-0.4}/src/digraphx/parametric.py +0 -0
  171. {digraphx-0.3 → digraphx-0.4}/src/digraphx/py.typed +0 -0
  172. {digraphx-0.3 → digraphx-0.4}/src/digraphx/tiny_digraph.py +0 -0
  173. {digraphx-0.3 → digraphx-0.4}/src/digraphx.egg-info/dependency_links.txt +0 -0
  174. {digraphx-0.3 → digraphx-0.4}/src/digraphx.egg-info/not-zip-safe +0 -0
  175. {digraphx-0.3 → digraphx-0.4}/src/digraphx.egg-info/requires.txt +0 -0
  176. {digraphx-0.3 → digraphx-0.4}/src/digraphx.egg-info/top_level.txt +0 -0
  177. {digraphx-0.3 → digraphx-0.4}/tests/__init__.py +0 -0
  178. {digraphx-0.3 → digraphx-0.4}/tests/conftest.py +0 -0
  179. {digraphx-0.3 → digraphx-0.4}/tests/test_cycle_ratio.py +0 -0
  180. {digraphx-0.3 → digraphx-0.4}/tests/test_min_cycle_ratio_property.py +0 -0
  181. {digraphx-0.3 → digraphx-0.4}/tests/test_min_parametric_q.py +0 -0
  182. {digraphx-0.3 → digraphx-0.4}/tests/test_neg_cycle.py +0 -0
  183. {digraphx-0.3 → digraphx-0.4}/tests/test_neg_cycle_q.py +0 -0
  184. {digraphx-0.3 → digraphx-0.4}/tests/test_parametric.py +0 -0
  185. {digraphx-0.3 → digraphx-0.4}/tests/test_timing_example.py +0 -0
  186. {digraphx-0.3 → digraphx-0.4}/tests/test_tiny_digraph.py +0 -0
  187. {digraphx-0.3 → digraphx-0.4}/tests/test_tiny_digraph_property.py +0 -0
  188. {digraphx-0.3 → digraphx-0.4}/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
@@ -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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: digraphx
3
- Version: 0.3
3
+ Version: 0.4
4
4
  Summary: Network Optimization Python Code
5
5
  Home-page: https://github.com/luk036/digraphx
6
6
  Author: Wai-Shing Luk
@@ -42,11 +42,14 @@ useful in many different applications.
42
42
  """
43
43
 
44
44
  from fractions import Fraction
45
+ from math import floor
45
46
  from typing import (
47
+ Any,
46
48
  Callable,
47
49
  Dict,
48
50
  Generator,
49
51
  Generic,
52
+ Iterable,
50
53
  List,
51
54
  Mapping,
52
55
  MutableMapping,
@@ -62,6 +65,30 @@ Domain = TypeVar(
62
65
  ) # Numeric type for weights (must support comparison and arithmetic)
63
66
  Cycle = List[Arc] # Alias for a list of edges forming a cycle
64
67
 
68
+ # NOTE: get_weight return type is `Any` (not `Domain`) to match C++ template
69
+ # semantics where the Callable return type is independent from the dist value
70
+ # type (Mapping). C++ `digraphx-cpp` uses unconstrained template parameters:
71
+ #
72
+ # template <typename Mapping, typename Callable>
73
+ # auto howard(Mapping& dist, Callable get_weight);
74
+ #
75
+ # The `Domain` TypeVar only constrains dist values; get_weight may return a
76
+ # different numeric type (e.g., float weight with int dist). When dist[u] is
77
+ # int and the sum is float, int(floor(...)) truncates like C++ implicit
78
+ # double→int conversion on assignment.
79
+
80
+
81
+ def _view_items(container: Iterable[Tuple[Node, Arc]]) -> Iterable[Tuple[Node, Arc]]:
82
+ """Get a pair-iterable view of a neighbor container.
83
+
84
+ Mirrors C++ `_view_items` in digraphx-cpp/neg_cycle.hpp:
85
+ - For dict-like containers, use .items() to get (key, value) pairs
86
+ - For list-like containers, iterate directly (each element is a pair)
87
+ """
88
+ if isinstance(container, dict):
89
+ return container.items()
90
+ return container
91
+
65
92
 
66
93
  class NegCycleFinder(Generic[Node, Arc, Domain]):
67
94
  """Negative Cycle Finder by Howard's method
@@ -105,14 +132,14 @@ class NegCycleFinder(Generic[Node, Arc, Domain]):
105
132
  # Dictionary to store predecessor information (node -> (predecessor_node, edge))
106
133
  pred: Dict[Node, Tuple[Node, Arc]]
107
134
 
108
- def __init__(self, digraph: Mapping[Node, Mapping[Node, Arc]]) -> None:
135
+ def __init__(self, digraph: Mapping[Node, Iterable[Tuple[Node, Arc]]]) -> None:
109
136
  """Initialize the negative cycle finder with a directed graph.
110
137
 
111
138
  Args:
112
139
  digraph: A mapping representing a directed graph where:
113
140
  - Keys are source nodes
114
- - Values are mappings of destination nodes to edges
115
- Example: {u: {v: edge_uv, w: edge_uw}, v: {u: edge_vu}}
141
+ - Values are iterables of (target_node, edge) pairs
142
+ Example: {u: [(v, edge_uv), (w, edge_uw)], v: [(u, edge_vu)]}
116
143
  """
117
144
  self.digraph = digraph
118
145
  self.pred: Dict[Node, Tuple[Node, Arc]] = {}
@@ -155,7 +182,7 @@ class NegCycleFinder(Generic[Node, Arc, Domain]):
155
182
  def relax(
156
183
  self,
157
184
  dist: MutableMapping[Node, Domain],
158
- get_weight: Callable[[Arc], Domain],
185
+ get_weight: Callable[[Arc], Any],
159
186
  ) -> bool:
160
187
  """Perform one relaxation pass of the Bellman-Ford algorithm.
161
188
 
@@ -189,8 +216,11 @@ class NegCycleFinder(Generic[Node, Arc, Domain]):
189
216
  """
190
217
  changed = False
191
218
  for u_node, neighbors in self.digraph.items():
192
- for v_node, edge in neighbors.items():
193
- distance = dist[u_node] + get_weight(edge)
219
+ for v_node, edge in _view_items(neighbors):
220
+ tmp = dist[u_node] + get_weight(edge)
221
+ if isinstance(dist[u_node], int) and isinstance(tmp, float):
222
+ tmp = int(floor(tmp))
223
+ distance = tmp
194
224
  if dist[v_node] > distance: # Found a shorter path
195
225
  dist[v_node] = distance
196
226
  self.pred[v_node] = (u_node, edge) # Update predecessor
@@ -234,7 +264,7 @@ class NegCycleFinder(Generic[Node, Arc, Domain]):
234
264
  self,
235
265
  handle: Node,
236
266
  dist: MutableMapping[Node, Domain],
237
- get_weight: Callable[[Arc], Domain],
267
+ get_weight: Callable[[Arc], Any],
238
268
  ) -> bool:
239
269
  """Check if the cycle starting at 'handle' is negative.
240
270
 
@@ -268,9 +298,10 @@ class NegCycleFinder(Generic[Node, Arc, Domain]):
268
298
  # do while loop in C++
269
299
  while True:
270
300
  u_node, edge = self.pred[v_node]
271
- if dist[v_node] > dist[u_node] + get_weight(
272
- edge
273
- ): # Triangle inequality violated
301
+ tmp = dist[u_node] + get_weight(edge)
302
+ if isinstance(dist[u_node], int) and isinstance(tmp, float):
303
+ tmp = int(floor(tmp))
304
+ if dist[v_node] > tmp: # Triangle inequality violated
274
305
  return True
275
306
  v_node = u_node
276
307
  if v_node == handle: # Completed full cycle
@@ -280,7 +311,7 @@ class NegCycleFinder(Generic[Node, Arc, Domain]):
280
311
  def howard(
281
312
  self,
282
313
  dist: MutableMapping[Node, Domain],
283
- get_weight: Callable[[Arc], Domain],
314
+ get_weight: Callable[[Arc], Any],
284
315
  ) -> Generator[Cycle, None, None]:
285
316
  """Main algorithm to find negative cycles using Howard's method.
286
317
 
@@ -44,11 +44,14 @@ inconsistencies in systems modeled as graphs.
44
44
  """
45
45
 
46
46
  from fractions import Fraction
47
+ from math import floor
47
48
  from typing import (
49
+ Any,
48
50
  Callable,
49
51
  Dict,
50
52
  Generator,
51
53
  Generic,
54
+ Iterable,
52
55
  List,
53
56
  Mapping,
54
57
  MutableMapping,
@@ -66,6 +69,18 @@ Domain = TypeVar("Domain", int, Fraction, float) # Comparable Ring
66
69
  Cycle = List[Arc] # List of Arcs
67
70
 
68
71
 
72
+ def _view_items(container: Iterable[Tuple[Node, Arc]]) -> Iterable[Tuple[Node, Arc]]:
73
+ """Get a pair-iterable view of a neighbor container.
74
+
75
+ Mirrors C++ `_view_items` in digraphx-cpp/neg_cycle_q.hpp:
76
+ - For dict-like containers, use .items() to get (key, value) pairs
77
+ - For list-like containers, iterate directly (each element is a pair)
78
+ """
79
+ if isinstance(container, dict):
80
+ return container.items()
81
+ return container
82
+
83
+
69
84
  class NegCycleFinderQ(Generic[Node, Arc, Domain]):
70
85
  """Negative Cycle Finder with constraints by Howard's method
71
86
 
@@ -90,14 +105,14 @@ class NegCycleFinderQ(Generic[Node, Arc, Domain]):
90
105
  # Successor dictionary: maps each node to (successor_node, connecting_edge)
91
106
  succ: Dict[Node, Tuple[Node, Arc]]
92
107
 
93
- def __init__(self, digraph: Mapping[Node, Mapping[Node, Arc]]) -> None:
108
+ def __init__(self, digraph: Mapping[Node, Iterable[Tuple[Node, Arc]]]) -> None:
94
109
  """Initialize the negative cycle finder with a directed graph.
95
110
 
96
111
  Args:
97
112
  digraph: A directed graph represented as a nested mapping:
98
113
  - Outer keys: source nodes
99
- - Inner mappings: {target_node: edge} pairs
100
- Example: {u: {v: edge_uv, w: edge_uw}, v: {u: edge_vu}}
114
+ - Inner values: iterables of (target_node, edge) pairs
115
+ Example: {u: [(v, edge_uv), (w, edge_uw)], v: [(u, edge_vu)]}
101
116
  """
102
117
  self.digraph = digraph
103
118
  self.pred: Dict[Node, Tuple[Node, Arc]] = {}
@@ -147,7 +162,7 @@ class NegCycleFinderQ(Generic[Node, Arc, Domain]):
147
162
  def relax_pred(
148
163
  self,
149
164
  dist: MutableMapping[Node, Domain],
150
- get_weight: Callable[[Arc], Domain],
165
+ get_weight: Callable[[Arc], Any],
151
166
  update_ok: Callable[[Domain, Domain], bool],
152
167
  ) -> bool:
153
168
  """Perform predecessor relaxation step (Bellman-Ford style).
@@ -181,8 +196,11 @@ class NegCycleFinderQ(Generic[Node, Arc, Domain]):
181
196
  """
182
197
  changed = False
183
198
  for u_node, neighbors in self.digraph.items():
184
- for v_node, edge in neighbors.items():
185
- distance = dist[u_node] + get_weight(edge)
199
+ for v_node, edge in _view_items(neighbors):
200
+ tmp = dist[u_node] + get_weight(edge)
201
+ if isinstance(dist[u_node], int) and isinstance(tmp, float):
202
+ tmp = int(floor(tmp))
203
+ distance = tmp
186
204
  if dist[v_node] > distance and update_ok(dist[v_node], distance):
187
205
  dist[v_node] = distance
188
206
  self.pred[v_node] = (u_node, edge) # Update predecessor
@@ -192,7 +210,7 @@ class NegCycleFinderQ(Generic[Node, Arc, Domain]):
192
210
  def relax_succ(
193
211
  self,
194
212
  dist: MutableMapping[Node, Domain],
195
- get_weight: Callable[[Arc], Domain],
213
+ get_weight: Callable[[Arc], Any],
196
214
  update_ok: Callable[[Domain, Domain], bool],
197
215
  ) -> bool:
198
216
  """Perform successor relaxation step (reverse Bellman-Ford style).
@@ -223,8 +241,11 @@ class NegCycleFinderQ(Generic[Node, Arc, Domain]):
223
241
  """
224
242
  changed = False
225
243
  for u_node, neighbors in self.digraph.items():
226
- for v_node, edge in neighbors.items():
227
- distance = dist[v_node] - get_weight(edge)
244
+ for v_node, edge in _view_items(neighbors):
245
+ tmp = dist[v_node] - get_weight(edge)
246
+ if isinstance(dist[v_node], int) and isinstance(tmp, float):
247
+ tmp = int(floor(tmp))
248
+ distance = tmp
228
249
  if dist[u_node] < distance and update_ok(dist[u_node], distance):
229
250
  dist[u_node] = distance
230
251
  self.succ[u_node] = (v_node, edge) # Update successor
@@ -234,7 +255,7 @@ class NegCycleFinderQ(Generic[Node, Arc, Domain]):
234
255
  def howard_pred(
235
256
  self,
236
257
  dist: MutableMapping[Node, Domain],
237
- get_weight: Callable[[Arc], Domain],
258
+ get_weight: Callable[[Arc], Any],
238
259
  update_ok: Callable[[Domain, Domain], bool],
239
260
  ) -> Generator[Cycle, None, None]:
240
261
  """Find negative cycles using predecessor-based Howard's algorithm.
@@ -282,7 +303,7 @@ class NegCycleFinderQ(Generic[Node, Arc, Domain]):
282
303
  def howard_succ(
283
304
  self,
284
305
  dist: MutableMapping[Node, Domain],
285
- get_weight: Callable[[Arc], Domain],
306
+ get_weight: Callable[[Arc], Any],
286
307
  update_ok: Callable[[Domain, Domain], bool],
287
308
  ) -> Generator[Cycle, None, None]:
288
309
  """Find negative cycles using successor-based Howard's algorithm.
@@ -363,7 +384,7 @@ class NegCycleFinderQ(Generic[Node, Arc, Domain]):
363
384
  self,
364
385
  handle: Node,
365
386
  dist: MutableMapping[Node, Domain],
366
- get_weight: Callable[[Arc], Domain],
387
+ get_weight: Callable[[Arc], Any],
367
388
  ) -> bool:
368
389
  """Verify if the cycle starting at handle is negative.
369
390
 
@@ -396,8 +417,11 @@ class NegCycleFinderQ(Generic[Node, Arc, Domain]):
396
417
  # C-style do-while loop
397
418
  while True:
398
419
  u_node, edge = self.pred[v_node]
399
- if dist[v_node] > dist[u_node] + get_weight(edge):
400
- return True # Found negative cycle
420
+ tmp = dist[u_node] + get_weight(edge)
421
+ if isinstance(dist[u_node], int) and isinstance(tmp, float):
422
+ tmp = int(floor(tmp))
423
+ if dist[v_node] > tmp: # Found negative cycle
424
+ return True
401
425
  v_node = u_node
402
426
  if v_node == handle: # Completed full cycle
403
427
  break
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: digraphx
3
- Version: 0.3
3
+ Version: 0.4
4
4
  Summary: Network Optimization Python Code
5
5
  Home-page: https://github.com/luk036/digraphx
6
6
  Author: Wai-Shing Luk
@@ -0,0 +1,77 @@
1
+ .coveragerc
2
+ .gitignore
3
+ .isort.cfg
4
+ .pre-commit-config.yaml
5
+ .readthedocs.yml
6
+ AGENTS.md
7
+ AUTHORS.md
8
+ CHANGELOG.md
9
+ CONTRIBUTING.md
10
+ GEMINI.md
11
+ IFLOW.md
12
+ LICENSE
13
+ LICENSE.txt
14
+ README.md
15
+ coverage.json
16
+ environment.yml
17
+ mypy.ini
18
+ note.md
19
+ pyproject.toml
20
+ requirements.txt
21
+ setup.cfg
22
+ setup.py
23
+ tox.ini
24
+ .github/workflows/ci.bak
25
+ .github/workflows/jekyll-gh-pages.yml
26
+ .github/workflows/multi-platforms.yml
27
+ .github/workflows/python-app.yml
28
+ .github/workflows/python-publish.yml
29
+ .vscode/settings.json
30
+ docs/Makefile
31
+ docs/authors.md
32
+ docs/changelog.md
33
+ docs/conf.py
34
+ docs/contributing.md
35
+ docs/index.md
36
+ docs/license.md
37
+ docs/readme.md
38
+ docs/requirements.txt
39
+ docs/_static/.gitignore
40
+ experiments/plot_node_colormap.ipynb
41
+ experiments/plot_node_colormap.py
42
+ requirements/README.md
43
+ requirements/default.txt
44
+ requirements/doc.txt
45
+ requirements/test.txt
46
+ src/digraphx/__init__.py
47
+ src/digraphx/min_cycle_ratio.py
48
+ src/digraphx/min_parametric_q.py
49
+ src/digraphx/neg_cycle.py
50
+ src/digraphx/neg_cycle_q.py
51
+ src/digraphx/parametric.py
52
+ src/digraphx/py.typed
53
+ src/digraphx/tiny_digraph.py
54
+ src/digraphx.egg-info/PKG-INFO
55
+ src/digraphx.egg-info/SOURCES.txt
56
+ src/digraphx.egg-info/dependency_links.txt
57
+ src/digraphx.egg-info/not-zip-safe
58
+ src/digraphx.egg-info/requires.txt
59
+ src/digraphx.egg-info/top_level.txt
60
+ tests/__init__.py
61
+ tests/conftest.py
62
+ tests/test_cycle_ratio.py
63
+ tests/test_delay_padding_example_howard.py
64
+ tests/test_edge_cases.py
65
+ tests/test_howard_cycle_cancellation.py
66
+ tests/test_min_cycle_ratio_property.py
67
+ tests/test_min_parametric_q.py
68
+ tests/test_neg_cycle.py
69
+ tests/test_neg_cycle_q.py
70
+ tests/test_neg_cycle_q_simple_timing.py
71
+ tests/test_neg_cycle_simple_timing.py
72
+ tests/test_parametric.py
73
+ tests/test_stress.py
74
+ tests/test_timing_example.py
75
+ tests/test_timing_example_howard.py
76
+ tests/test_tiny_digraph.py
77
+ tests/test_tiny_digraph_property.py
@@ -83,7 +83,7 @@ def test_even1() -> None:
83
83
  "v2": {"v1": {"type": "h", "delay": 4}, "v3": {"type": "p", "delay": 0}},
84
84
  "v3": {"v1": {"type": "s", "delay": TCP - 3}},
85
85
  }
86
- dist: Dict[str, float] = {"v1": 0, "v2": 0, "v3": 0}
86
+ dist: Dict[str, float] = {"v1": 0.0, "v2": 0.0, "v3": 0.0}
87
87
  beta, num_iter, _ = even(digraph, 10, dist)
88
88
  assert num_iter < 10
89
89
  assert beta == approx(1.5)
@@ -108,7 +108,14 @@ def test_even2() -> None:
108
108
  "v4": {"v3": {"type": "s", "delay": TCP - 8}, "v5": {"type": "h", "delay": 3}},
109
109
  "v5": {"v4": {"type": "s", "delay": TCP - 3}},
110
110
  }
111
- dist: Dict[str, float] = {"v0": 0, "v1": 0, "v2": 0, "v3": 0, "v4": 0, "v5": 0}
111
+ dist: Dict[str, float] = {
112
+ "v0": 0.0,
113
+ "v1": 0.0,
114
+ "v2": 0.0,
115
+ "v3": 0.0,
116
+ "v4": 0.0,
117
+ "v5": 0.0,
118
+ }
112
119
  beta, num_iter, _ = even(digraph, 10, dist)
113
120
  print("slack02(s) = {}".format(TCP - 7 + dist["v2"] - dist["v0"]))
114
121
  print("slack02(h) = {}".format(6 + dist["v0"] - dist["v2"]))
@@ -11,7 +11,7 @@ from digraphx.neg_cycle import NegCycleFinder
11
11
  from digraphx.tiny_digraph import DiGraphAdapter, TinyDiGraph
12
12
 
13
13
 
14
- def test_empty_graph_neg_cycle():
14
+ def test_empty_graph_neg_cycle() -> None:
15
15
  """Test negative cycle finder on completely empty graph."""
16
16
  graph = DiGraphAdapter()
17
17
  dist: Dict[str, Fraction] = {}
@@ -21,7 +21,7 @@ def test_empty_graph_neg_cycle():
21
21
  assert len(cycles) == 0
22
22
 
23
23
 
24
- def test_single_node_no_edges():
24
+ def test_single_node_no_edges() -> None:
25
25
  """Test graph with single node and no edges."""
26
26
  graph = DiGraphAdapter()
27
27
  graph.add_node(0)
@@ -32,7 +32,7 @@ def test_single_node_no_edges():
32
32
  assert len(cycles) == 0
33
33
 
34
34
 
35
- def test_single_node_self_loop_negative():
35
+ def test_single_node_self_loop_negative() -> None:
36
36
  """Test single node with negative self-loop."""
37
37
  graph = DiGraphAdapter()
38
38
  graph.add_edge(0, 0, weight=-1)
@@ -43,7 +43,7 @@ def test_single_node_self_loop_negative():
43
43
  assert len(cycles) > 0
44
44
 
45
45
 
46
- def test_single_node_self_loop_positive():
46
+ def test_single_node_self_loop_positive() -> None:
47
47
  """Test single node with positive self-loop."""
48
48
  graph = DiGraphAdapter()
49
49
  graph.add_edge(0, 0, weight=5)
@@ -54,7 +54,7 @@ def test_single_node_self_loop_positive():
54
54
  assert len(cycles) == 0
55
55
 
56
56
 
57
- def test_disconnected_components():
57
+ def test_disconnected_components() -> None:
58
58
  """Test graph with multiple disconnected components."""
59
59
  graph = DiGraphAdapter()
60
60
 
@@ -75,7 +75,7 @@ def test_disconnected_components():
75
75
  assert len(cycles) > 0
76
76
 
77
77
 
78
- def test_tiny_digraph_init_zero_nodes():
78
+ def test_tiny_digraph_init_zero_nodes() -> None:
79
79
  """Test TinyDiGraph initialization with zero nodes."""
80
80
  graph = TinyDiGraph()
81
81
  graph.init_nodes(0)
@@ -84,7 +84,7 @@ def test_tiny_digraph_init_zero_nodes():
84
84
  assert graph.number_of_edges() == 0
85
85
 
86
86
 
87
- def test_tiny_digraph_add_edge_before_init():
87
+ def test_tiny_digraph_add_edge_before_init() -> None:
88
88
  """Test adding edge before initializing nodes."""
89
89
  graph = TinyDiGraph()
90
90
 
@@ -92,7 +92,7 @@ def test_tiny_digraph_add_edge_before_init():
92
92
  graph.add_edge(0, 1, weight=1)
93
93
 
94
94
 
95
- def test_non_numeric_weights():
95
+ def test_non_numeric_weights() -> None:
96
96
  """Test that algorithm works with non-numeric weight types."""
97
97
  graph = DiGraphAdapter()
98
98
  graph.add_edge("a", "b", weight=Fraction(1, 2))
@@ -105,7 +105,7 @@ def test_non_numeric_weights():
105
105
  assert len(cycles) > 0
106
106
 
107
107
 
108
- def test_mixed_node_types():
108
+ def test_mixed_node_types() -> None:
109
109
  """Test graph with mixed node types (strings and ints)."""
110
110
  graph = DiGraphAdapter()
111
111
  graph.add_edge("a", 0, weight=1)
@@ -118,7 +118,7 @@ def test_mixed_node_types():
118
118
  assert len(cycles) > 0
119
119
 
120
120
 
121
- def test_zero_weight_cycle():
121
+ def test_zero_weight_cycle() -> None:
122
122
  """Test cycle with all zero weights."""
123
123
  graph = DiGraphAdapter()
124
124
  graph.add_edge(0, 1, weight=0)
@@ -131,7 +131,7 @@ def test_zero_weight_cycle():
131
131
  assert len(cycles) == 0 # Zero cycle is not negative
132
132
 
133
133
 
134
- def test_very_large_weights():
134
+ def test_very_large_weights() -> None:
135
135
  """Test with very large weight values."""
136
136
  graph = DiGraphAdapter()
137
137
  large_weight = 10**15
@@ -145,7 +145,7 @@ def test_very_large_weights():
145
145
  assert len(cycles) > 0
146
146
 
147
147
 
148
- def test_digraph_adapter_mixed_edge_attributes():
148
+ def test_digraph_adapter_mixed_edge_attributes() -> None:
149
149
  """Test DiGraphAdapter with mixed edge attributes."""
150
150
  graph = DiGraphAdapter()
151
151
  graph.add_edge("a", "b", weight=5, cost=10, time=2, label="edge1")
@@ -157,7 +157,7 @@ def test_digraph_adapter_mixed_edge_attributes():
157
157
  assert graph["a"]["b"]["label"] == "edge1"
158
158
 
159
159
 
160
- def test_min_cycle_ratio_empty_graph():
160
+ def test_min_cycle_ratio_empty_graph() -> None:
161
161
  """Test minimum cycle ratio solver on empty graph."""
162
162
  graph = DiGraphAdapter()
163
163
  dist: Dict[str, Fraction] = {}
@@ -170,7 +170,7 @@ def test_min_cycle_ratio_empty_graph():
170
170
  assert len(cycle) == 0
171
171
 
172
172
 
173
- def test_min_cycle_ratio_single_node():
173
+ def test_min_cycle_ratio_single_node() -> None:
174
174
  """Test minimum cycle ratio solver on single node graph."""
175
175
  graph = DiGraphAdapter()
176
176
  graph.add_edge("a", "a", cost=5, time=1)
@@ -183,7 +183,7 @@ def test_min_cycle_ratio_single_node():
183
183
  assert isinstance(ratio, (Fraction, float))
184
184
 
185
185
 
186
- def test_invalid_weight_extraction():
186
+ def test_invalid_weight_extraction() -> None:
187
187
  """Test handling of edges with missing weight attributes."""
188
188
  graph = DiGraphAdapter()
189
189
  graph.add_edge("a", "b", weight=5)
@@ -195,7 +195,7 @@ def test_invalid_weight_extraction():
195
195
  assert len(cycles) == 0
196
196
 
197
197
 
198
- def test_directed_graph_orientation():
198
+ def test_directed_graph_orientation() -> None:
199
199
  """Test that algorithm respects edge direction."""
200
200
  graph = DiGraphAdapter()
201
201
  graph.add_edge(0, 1, weight=1)
@@ -208,7 +208,7 @@ def test_directed_graph_orientation():
208
208
  assert len(cycles) > 0 # 0->1->2->0 forms negative cycle
209
209
 
210
210
 
211
- def test_multiple_edges_same_nodes():
211
+ def test_multiple_edges_same_nodes() -> None:
212
212
  """Test graph with multiple edges between same node pair (parallel edges)."""
213
213
  graph = DiGraphAdapter()
214
214
  # NetworkX typically replaces parallel edges, but test behavior
@@ -221,7 +221,7 @@ def test_multiple_edges_same_nodes():
221
221
  assert len(cycles) == 0 # Single edge can't form cycle
222
222
 
223
223
 
224
- def test_floating_point_weights():
224
+ def test_floating_point_weights() -> None:
225
225
  """Test with floating point weights."""
226
226
  graph = DiGraphAdapter()
227
227
  graph.add_edge(0, 1, weight=0.1)
@@ -234,7 +234,7 @@ def test_floating_point_weights():
234
234
  assert len(cycles) > 0
235
235
 
236
236
 
237
- def test_negative_self_loop():
237
+ def test_negative_self_loop() -> None:
238
238
  """Test negative self-loop detection."""
239
239
  graph = DiGraphAdapter()
240
240
  graph.add_edge(0, 1, weight=1)
@@ -247,7 +247,7 @@ def test_negative_self_loop():
247
247
  assert len(cycles) > 0
248
248
 
249
249
 
250
- def test_no_path_between_components():
250
+ def test_no_path_between_components() -> None:
251
251
  """Test that negative cycle in one component is detected even when other components exist."""
252
252
  graph = DiGraphAdapter()
253
253
  # Component 1: no cycle
@@ -262,3 +262,64 @@ def test_no_path_between_components():
262
262
  finder = NegCycleFinder(graph)
263
263
  cycles = list(finder.howard(dist, lambda edge: edge.get("weight", 1)))
264
264
  assert len(cycles) > 0
265
+
266
+
267
+ # ── Multi-edge (parallel edge) tests ─────────────────────────────────────────
268
+
269
+
270
+ def test_multi_edge_list_of_lists_no_cycle() -> None:
271
+ """Multi-edge graph using list-of-lists (like C++ test_neg_cycle_raw.cpp).
272
+
273
+ Two parallel edges from node 2 to node 0, none forming a negative cycle.
274
+ """
275
+ digraph = {
276
+ 0: [(1, 7.0), (2, 5.0)],
277
+ 1: [(0, 0.0), (2, 3.0)],
278
+ 2: [(1, 1.0), (0, 2.0), (0, 1.0)], # two parallel edges 2→0
279
+ }
280
+ dist = {0: 0.0, 1: 0.0, 2: 0.0}
281
+ finder: NegCycleFinder[int, float, float] = NegCycleFinder(digraph)
282
+ cycles = list(finder.howard(dist, lambda w: w))
283
+ assert len(cycles) == 0
284
+
285
+
286
+ def test_multi_edge_list_of_lists_with_cycle() -> None:
287
+ """Multi-edge graph with a negative cycle formed by parallel edges."""
288
+ digraph = {
289
+ 0: [(1, 1.0)],
290
+ 1: [(2, 1.0)],
291
+ 2: [(0, -3.0), (0, 2.0)], # one negative, one positive parallel edge
292
+ }
293
+ dist = {0: 0.0, 1: 0.0, 2: 0.0}
294
+ finder: NegCycleFinder[int, float, float] = NegCycleFinder(digraph)
295
+ cycles = list(finder.howard(dist, lambda w: w))
296
+ assert len(cycles) > 0
297
+
298
+
299
+ def test_multi_edge_q_variant() -> None:
300
+ """NegCycleFinderQ with multi-edge graph (predecessor variant)."""
301
+ from digraphx.neg_cycle_q import NegCycleFinderQ
302
+
303
+ digraph = {
304
+ 0: [(1, 7.0), (2, 5.0)],
305
+ 1: [(0, 0.0), (2, 3.0)],
306
+ 2: [(1, 1.0), (0, 2.0), (0, 1.0)], # two parallel edges 2→0
307
+ }
308
+ dist = {0: 0.0, 1: 0.0, 2: 0.0}
309
+ update_ok = lambda old, new: True
310
+ finder: NegCycleFinderQ[int, float, float] = NegCycleFinderQ(digraph)
311
+ cycles = list(finder.howard_pred(dist, lambda w: w, update_ok))
312
+ assert len(cycles) == 0
313
+
314
+
315
+ def test_multi_edge_dict_backward_compat() -> None:
316
+ """Dict-based graphs still work (backward compatibility)."""
317
+ digraph = {
318
+ 0: {1: 7.0, 2: 5.0},
319
+ 1: {0: 0.0, 2: 3.0},
320
+ 2: {1: 1.0, 0: 2.0},
321
+ }
322
+ dist = {0: 0.0, 1: 0.0, 2: 0.0}
323
+ finder: NegCycleFinder[int, float, float] = NegCycleFinder(digraph)
324
+ cycles = list(finder.howard(dist, lambda w: w))
325
+ assert len(cycles) == 0
@@ -31,7 +31,7 @@ def test_even() -> None:
31
31
  "v2": {"v3": TCP - 3.0, "v1": 2.0},
32
32
  "v3": {"v1": TCP - 4.0, "v2": 3.0},
33
33
  }
34
- dist: Dict[str, float] = {"v1": 0, "v2": 0, "v3": 0}
34
+ dist: Dict[str, float] = {"v1": 0.0, "v2": 0.0, "v3": 0.0}
35
35
  beta, num_iter = even(digraph, 10, dist)
36
36
  assert num_iter < 4
37
37
  assert beta == approx(1.0)
@@ -76,7 +76,7 @@ def test_prop() -> None:
76
76
  "v2": {"cost": 3.0, "time": 1.5},
77
77
  },
78
78
  }
79
- dist: Dict[str, float] = {"v1": 0, "v2": 0, "v3": 0}
79
+ dist: Dict[str, float] = {"v1": 0.0, "v2": 0.0, "v3": 0.0}
80
80
  beta, num_iter = prop(digraph, 10, dist)
81
81
  assert num_iter < 5
82
82
  assert beta == approx(0.3448275862068966)
@@ -22,17 +22,17 @@ def has_negative_cycle(TCP: float, dist: Dict[str, float]) -> bool:
22
22
 
23
23
  def test_simple_timing_example1() -> None:
24
24
  """Creates a test graph for timing tests."""
25
- dist: Dict[str, float] = {"v1": 0, "v2": 0, "v3": 0}
25
+ dist: Dict[str, float] = {"v1": 0.0, "v2": 0.0, "v3": 0.0}
26
26
  assert has_negative_cycle(4.0, dist) is False
27
27
 
28
28
 
29
29
  def test_simple_timing_example2() -> None:
30
30
  """Creates a test graph for timing tests."""
31
- dist: Dict[str, float] = {"v1": 0, "v2": 0, "v3": 0}
31
+ dist: Dict[str, float] = {"v1": 0.0, "v2": 0.0, "v3": 0.0}
32
32
  assert has_negative_cycle(2.0, dist) is True
33
33
 
34
34
 
35
35
  def test_simple_timing_example3() -> None:
36
- dist: Dict[str, float] = {"v1": 0, "v2": 0, "v3": 0}
36
+ dist: Dict[str, float] = {"v1": 0.0, "v2": 0.0, "v3": 0.0}
37
37
  assert has_negative_cycle(3.0, dist) is False
38
38
  assert dist == {"v1": 0, "v2": 1, "v3": 1}