digraphx 0.6__tar.gz → 0.7__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.
- {digraphx-0.6 → digraphx-0.7}/CHANGELOG.md +19 -0
- {digraphx-0.6/src/digraphx.egg-info → digraphx-0.7}/PKG-INFO +1 -1
- digraphx-0.7/benchmarks/test_benchmarks.py +131 -0
- {digraphx-0.6 → digraphx-0.7}/src/digraphx/_cycle_base.py +75 -2
- {digraphx-0.6 → digraphx-0.7}/src/digraphx/_parametric_base.py +1 -4
- {digraphx-0.6 → digraphx-0.7}/src/digraphx/neg_cycle.py +12 -1
- {digraphx-0.6 → digraphx-0.7}/src/digraphx/neg_cycle_q.py +24 -5
- {digraphx-0.6 → digraphx-0.7}/src/digraphx/tiny_digraph.py +34 -6
- {digraphx-0.6 → digraphx-0.7/src/digraphx.egg-info}/PKG-INFO +1 -1
- {digraphx-0.6 → digraphx-0.7}/src/digraphx.egg-info/SOURCES.txt +2 -0
- {digraphx-0.6 → digraphx-0.7}/src/digraphx.egg-info/scm_file_list.json +3 -1
- digraphx-0.7/src/digraphx.egg-info/scm_version.json +8 -0
- {digraphx-0.6 → digraphx-0.7}/tests/test_neg_cycle.py +29 -0
- digraphx-0.7/tunable_parameter.md +153 -0
- digraphx-0.6/src/digraphx.egg-info/scm_version.json +0 -8
- {digraphx-0.6 → digraphx-0.7}/.coveragerc +0 -0
- {digraphx-0.6 → digraphx-0.7}/.github/workflows/jekyll-gh-pages.yml +0 -0
- {digraphx-0.6 → digraphx-0.7}/.github/workflows/multi-platforms.yml +0 -0
- {digraphx-0.6 → digraphx-0.7}/.github/workflows/python-app.yml +0 -0
- {digraphx-0.6 → digraphx-0.7}/.github/workflows/python-publish.yml +0 -0
- {digraphx-0.6 → digraphx-0.7}/.gitignore +0 -0
- {digraphx-0.6 → digraphx-0.7}/.isort.cfg +0 -0
- {digraphx-0.6 → digraphx-0.7}/.pre-commit-config.yaml +0 -0
- {digraphx-0.6 → digraphx-0.7}/.readthedocs.yml +0 -0
- {digraphx-0.6 → digraphx-0.7}/.vscode/settings.json +0 -0
- {digraphx-0.6 → digraphx-0.7}/AGENTS.md +0 -0
- {digraphx-0.6 → digraphx-0.7}/AUTHORS.md +0 -0
- {digraphx-0.6 → digraphx-0.7}/CONTRIBUTING.md +0 -0
- {digraphx-0.6 → digraphx-0.7}/GEMINI.md +0 -0
- {digraphx-0.6 → digraphx-0.7}/LICENSE.txt +0 -0
- {digraphx-0.6 → digraphx-0.7}/README.md +0 -0
- {digraphx-0.6 → digraphx-0.7}/coverage.json +0 -0
- {digraphx-0.6 → digraphx-0.7}/docs/Makefile +0 -0
- {digraphx-0.6 → digraphx-0.7}/docs/_static/.gitignore +0 -0
- {digraphx-0.6 → digraphx-0.7}/docs/authors.md +0 -0
- {digraphx-0.6 → digraphx-0.7}/docs/changelog.md +0 -0
- {digraphx-0.6 → digraphx-0.7}/docs/conf.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/docs/contributing.md +0 -0
- {digraphx-0.6 → digraphx-0.7}/docs/examples/plot_cycle_detection.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/docs/examples/plot_tiny_digraph.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/docs/figures_demo.md +0 -0
- {digraphx-0.6 → digraphx-0.7}/docs/index.md +0 -0
- {digraphx-0.6 → digraphx-0.7}/docs/license.md +0 -0
- {digraphx-0.6 → digraphx-0.7}/docs/readme.md +0 -0
- {digraphx-0.6 → digraphx-0.7}/docs/requirements.txt +0 -0
- {digraphx-0.6 → digraphx-0.7}/environment.yml +0 -0
- {digraphx-0.6 → digraphx-0.7}/experimental/__init__.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/experimental/spare_tsv.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/experimental/test_spare_tsv.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/experiments/experi-descent-new.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/experiments/experi-descent-old.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/experiments/experi-descent.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/experiments/experi.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/experiments/plot_node_colormap.ipynb +0 -0
- {digraphx-0.6 → digraphx-0.7}/experiments/plot_node_colormap.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/mypy.ini +0 -0
- {digraphx-0.6 → digraphx-0.7}/note.md +0 -0
- {digraphx-0.6 → digraphx-0.7}/pyproject.toml +0 -0
- {digraphx-0.6 → digraphx-0.7}/requirements/README.md +0 -0
- {digraphx-0.6 → digraphx-0.7}/requirements/default.txt +0 -0
- {digraphx-0.6 → digraphx-0.7}/requirements/doc.txt +0 -0
- {digraphx-0.6 → digraphx-0.7}/requirements/test.txt +0 -0
- {digraphx-0.6 → digraphx-0.7}/requirements.txt +0 -0
- {digraphx-0.6 → digraphx-0.7}/setup.cfg +0 -0
- {digraphx-0.6 → digraphx-0.7}/setup.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/src/digraphx/__init__.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/src/digraphx/csr_digraph.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/src/digraphx/mcf.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/src/digraphx/min_cycle_ratio.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/src/digraphx/min_parametric_q.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/src/digraphx/parametric.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/src/digraphx/py.typed +0 -0
- {digraphx-0.6 → digraphx-0.7}/src/digraphx.egg-info/dependency_links.txt +0 -0
- {digraphx-0.6 → digraphx-0.7}/src/digraphx.egg-info/not-zip-safe +0 -0
- {digraphx-0.6 → digraphx-0.7}/src/digraphx.egg-info/requires.txt +0 -0
- {digraphx-0.6 → digraphx-0.7}/src/digraphx.egg-info/top_level.txt +0 -0
- {digraphx-0.6 → digraphx-0.7}/tests/__init__.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/tests/conftest.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/tests/test_coverage_extras.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/tests/test_csr_digraph.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/tests/test_cycle_ratio.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/tests/test_delay_padding_example_howard.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/tests/test_edge_cases.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/tests/test_howard_cycle_cancellation.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/tests/test_mcf.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/tests/test_min_cycle_ratio_extra.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/tests/test_min_cycle_ratio_property.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/tests/test_min_parametric_q.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/tests/test_min_parametric_q_extra.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/tests/test_neg_cycle_q.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/tests/test_neg_cycle_q_simple_timing.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/tests/test_neg_cycle_simple_timing.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/tests/test_parametric.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/tests/test_stress.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/tests/test_timing_example.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/tests/test_timing_example_howard.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/tests/test_tiny_digraph.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/tests/test_tiny_digraph_property.py +0 -0
- {digraphx-0.6 → digraphx-0.7}/tox.ini +0 -0
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Version 0.7 (2026-10-09)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
- **Optional `max_iter` cap for Howard's method**: `howard` / `howard_pred` / `howard_succ` accept an optional `max_iter` (default `None` = unbounded); a set cap raises `RuntimeError` instead of looping forever on degenerate graphs. (#8669cdd)
|
|
7
|
+
|
|
8
|
+
### Performance
|
|
9
|
+
- **Fast-path `TinyDiGraph.add_edge`**: Lazy-slot resolution avoids unnecessary work on edge insertion. (#a7ed9c3)
|
|
10
|
+
- **Pre-weighted edge list reuse**: Howard relaxation reuses the pre-weighted edge list instead of recomputing it. (#da93374)
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
- **`cheat_adjlist_outer_dict` doctest**: Aligned the doctest with the lazy-sentinel behavior. (#20e7c11)
|
|
14
|
+
|
|
15
|
+
### Testing & Code Quality
|
|
16
|
+
- **Runtime benchmarks**: Added benchmarks for the core graph algorithms. (#0c7329a)
|
|
17
|
+
- **Neg-cycle tests**: Added tests covering the `max_iter` cap. (#8669cdd)
|
|
18
|
+
|
|
19
|
+
### Documentation
|
|
20
|
+
- **Tunable parameter reference**: Added `tunable_parameter.md`. (#9e0639b)
|
|
21
|
+
|
|
3
22
|
## Version 0.6 (2026-09-04)
|
|
4
23
|
|
|
5
24
|
### Features
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""Runtime benchmarks for digraphx core algorithms.
|
|
2
|
+
|
|
3
|
+
Run explicitly (they are outside the default ``testpaths`` so ``pytest`` alone
|
|
4
|
+
skips them):
|
|
5
|
+
|
|
6
|
+
pytest benchmarks/ --benchmark-only
|
|
7
|
+
|
|
8
|
+
Uses fixed seeds so numbers are comparable across runs and across machines.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import random
|
|
12
|
+
from fractions import Fraction
|
|
13
|
+
|
|
14
|
+
import networkx as nx
|
|
15
|
+
import pytest
|
|
16
|
+
|
|
17
|
+
from digraphx.min_cycle_ratio import MinCycleRatioSolver, set_default
|
|
18
|
+
from digraphx.neg_cycle import NegCycleFinder
|
|
19
|
+
from digraphx.tiny_digraph import DiGraphAdapter, TinyDiGraph
|
|
20
|
+
|
|
21
|
+
SEED = 1234
|
|
22
|
+
SIZES = [200, 1000]
|
|
23
|
+
MCR_SIZES = [50, 100]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def weighted_graph(n: int, deg: int, seed: int = SEED) -> dict:
|
|
27
|
+
rng = random.Random(seed)
|
|
28
|
+
g: dict = {i: {} for i in range(n)}
|
|
29
|
+
for i in range(n):
|
|
30
|
+
for _ in range(deg):
|
|
31
|
+
j = rng.randrange(n)
|
|
32
|
+
if j != i:
|
|
33
|
+
g[i][j] = rng.randint(1, 9)
|
|
34
|
+
for i in range(n - 1):
|
|
35
|
+
g[i][i + 1] = 1
|
|
36
|
+
g[n - 1][0] = -n
|
|
37
|
+
return g
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def ratio_graph(n: int, deg: int, seed: int = SEED) -> DiGraphAdapter:
|
|
41
|
+
rng = random.Random(seed)
|
|
42
|
+
g = DiGraphAdapter()
|
|
43
|
+
g.add_nodes_from(range(n))
|
|
44
|
+
for i in range(n):
|
|
45
|
+
for _ in range(deg):
|
|
46
|
+
j = rng.randrange(n)
|
|
47
|
+
if j != i:
|
|
48
|
+
g.add_edge(i, j, cost=rng.randint(-5, 9), time=rng.randint(1, 5))
|
|
49
|
+
return g
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@pytest.mark.parametrize("n", SIZES)
|
|
53
|
+
def test_bench_tiny_build(benchmark, n: int) -> None:
|
|
54
|
+
deg = 5
|
|
55
|
+
|
|
56
|
+
def build() -> TinyDiGraph:
|
|
57
|
+
g = TinyDiGraph()
|
|
58
|
+
g.init_nodes(n)
|
|
59
|
+
for i in range(n):
|
|
60
|
+
for j in range(1, deg + 1):
|
|
61
|
+
g.add_edge(i, (i + j) % n, weight=1)
|
|
62
|
+
return g
|
|
63
|
+
|
|
64
|
+
benchmark(build)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@pytest.mark.parametrize("n", SIZES)
|
|
68
|
+
def test_bench_adapter_build(benchmark, n: int) -> None:
|
|
69
|
+
deg = 5
|
|
70
|
+
|
|
71
|
+
def build() -> DiGraphAdapter:
|
|
72
|
+
g = DiGraphAdapter()
|
|
73
|
+
g.add_nodes_from(range(n))
|
|
74
|
+
for i in range(n):
|
|
75
|
+
for j in range(1, deg + 1):
|
|
76
|
+
g.add_edge(i, (i + j) % n, weight=1)
|
|
77
|
+
return g
|
|
78
|
+
|
|
79
|
+
benchmark(build)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@pytest.mark.parametrize("n", SIZES)
|
|
83
|
+
def test_bench_howard_negative_cycle(benchmark, n: int) -> None:
|
|
84
|
+
g = weighted_graph(n, 6)
|
|
85
|
+
finder = NegCycleFinder(g)
|
|
86
|
+
|
|
87
|
+
def run() -> None:
|
|
88
|
+
dist = {v: 0 for v in g}
|
|
89
|
+
list(finder.howard(dist, lambda e: e))
|
|
90
|
+
|
|
91
|
+
benchmark(run)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@pytest.mark.parametrize("n", SIZES)
|
|
95
|
+
def test_bench_networkx_negative_edge_cycle(benchmark, n: int) -> None:
|
|
96
|
+
g = weighted_graph(n, 6)
|
|
97
|
+
h = nx.DiGraph()
|
|
98
|
+
for u, nb in g.items():
|
|
99
|
+
h.add_node(u)
|
|
100
|
+
for v, w in nb.items():
|
|
101
|
+
h.add_edge(u, v, weight=w)
|
|
102
|
+
|
|
103
|
+
benchmark(lambda: nx.negative_edge_cycle(h, weight="weight"))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@pytest.mark.parametrize("n", MCR_SIZES)
|
|
107
|
+
def test_bench_mcr_fraction(benchmark, n: int) -> None:
|
|
108
|
+
g = ratio_graph(n, 5)
|
|
109
|
+
set_default(g, "cost", 0)
|
|
110
|
+
set_default(g, "time", 1)
|
|
111
|
+
solver = MinCycleRatioSolver(g)
|
|
112
|
+
|
|
113
|
+
def run():
|
|
114
|
+
dist = {v: Fraction(0) for v in g}
|
|
115
|
+
return solver.run(dist, Fraction(10))
|
|
116
|
+
|
|
117
|
+
benchmark(run)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@pytest.mark.parametrize("n", MCR_SIZES)
|
|
121
|
+
def test_bench_mcr_float(benchmark, n: int) -> None:
|
|
122
|
+
g = ratio_graph(n, 5)
|
|
123
|
+
set_default(g, "cost", 0)
|
|
124
|
+
set_default(g, "time", 1)
|
|
125
|
+
solver = MinCycleRatioSolver(g)
|
|
126
|
+
|
|
127
|
+
def run():
|
|
128
|
+
dist = {v: 0.0 for v in g}
|
|
129
|
+
return solver.run(dist, 10.0)
|
|
130
|
+
|
|
131
|
+
benchmark(run)
|
|
@@ -17,6 +17,7 @@ from typing import (
|
|
|
17
17
|
List,
|
|
18
18
|
Mapping,
|
|
19
19
|
MutableMapping,
|
|
20
|
+
Optional,
|
|
20
21
|
Tuple,
|
|
21
22
|
TypeVar,
|
|
22
23
|
)
|
|
@@ -110,6 +111,57 @@ def relax_succ(
|
|
|
110
111
|
return changed
|
|
111
112
|
|
|
112
113
|
|
|
114
|
+
def prepare_edges(
|
|
115
|
+
digraph: Mapping[Node, Mapping[Node, Arc]],
|
|
116
|
+
get_weight: Callable[[Arc], Domain],
|
|
117
|
+
) -> List[Tuple[Node, Node, Arc, Domain]]:
|
|
118
|
+
"""Flatten the graph into ``(u, v, edge, weight)`` tuples.
|
|
119
|
+
|
|
120
|
+
``get_weight`` is evaluated exactly once per edge. Callers that keep the
|
|
121
|
+
ratio (and hence every edge weight) fixed for the whole search can reuse the
|
|
122
|
+
result across many relaxation passes instead of recomputing it each time.
|
|
123
|
+
"""
|
|
124
|
+
return [
|
|
125
|
+
(utx, vtx, edge, get_weight(edge))
|
|
126
|
+
for utx, neighbors in digraph.items()
|
|
127
|
+
for vtx, edge in neighbors.items()
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def relax_pred_flat(
|
|
132
|
+
edges: List[Tuple[Node, Node, Arc, Domain]],
|
|
133
|
+
dist: MutableMapping[Node, Domain],
|
|
134
|
+
update_ok: Callable[[Domain, Domain], bool],
|
|
135
|
+
pred: PointTo,
|
|
136
|
+
) -> bool:
|
|
137
|
+
"""Predecessor relaxation over a pre-flattened, pre-weighted edge list."""
|
|
138
|
+
changed = False
|
|
139
|
+
for utx, vtx, edge, weight in edges:
|
|
140
|
+
distance = dist[utx] + weight
|
|
141
|
+
if dist[vtx] > distance and update_ok(dist[vtx], distance):
|
|
142
|
+
dist[vtx] = distance
|
|
143
|
+
pred[vtx] = (utx, edge)
|
|
144
|
+
changed = True
|
|
145
|
+
return changed
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def relax_succ_flat(
|
|
149
|
+
edges: List[Tuple[Node, Node, Arc, Domain]],
|
|
150
|
+
dist: MutableMapping[Node, Domain],
|
|
151
|
+
update_ok: Callable[[Domain, Domain], bool],
|
|
152
|
+
succ: PointTo,
|
|
153
|
+
) -> bool:
|
|
154
|
+
"""Successor relaxation over a pre-flattened, pre-weighted edge list."""
|
|
155
|
+
changed = False
|
|
156
|
+
for utx, vtx, edge, weight in edges:
|
|
157
|
+
distance = dist[vtx] - weight
|
|
158
|
+
if dist[utx] < distance and update_ok(dist[utx], distance):
|
|
159
|
+
dist[utx] = distance
|
|
160
|
+
succ[utx] = (vtx, edge)
|
|
161
|
+
changed = True
|
|
162
|
+
return changed
|
|
163
|
+
|
|
164
|
+
|
|
113
165
|
def cycle_list(point_to: PointTo, handle: Node) -> Cycle:
|
|
114
166
|
"""Reconstruct the cycle starting from ``handle`` in the point-to map.
|
|
115
167
|
|
|
@@ -157,6 +209,7 @@ def howard_search(
|
|
|
157
209
|
point_to: PointTo,
|
|
158
210
|
direction: str,
|
|
159
211
|
verify: bool = True,
|
|
212
|
+
max_iter: Optional[int] = None,
|
|
160
213
|
) -> Generator[Cycle, None, None]:
|
|
161
214
|
"""Template Method: Howard's policy-iteration skeleton.
|
|
162
215
|
|
|
@@ -167,14 +220,34 @@ def howard_search(
|
|
|
167
220
|
(``"pred"`` for predecessor relaxation, ``"succ"`` for successor). When
|
|
168
221
|
``verify`` is ``True``, each candidate cycle is asserted to be negative
|
|
169
222
|
before being yielded (matching the predecessor variants).
|
|
223
|
+
|
|
224
|
+
``max_iter`` optionally caps the number of relaxation rounds. ``None``
|
|
225
|
+
(default) means unbounded; when set and exceeded without finding a negative
|
|
226
|
+
cycle, ``RuntimeError`` is raised rather than looping forever.
|
|
170
227
|
"""
|
|
171
228
|
point_to.clear()
|
|
172
229
|
found = False
|
|
173
|
-
|
|
174
|
-
|
|
230
|
+
num_iter = 0
|
|
231
|
+
edges = prepare_edges(digraph, get_weight)
|
|
232
|
+
if direction == "pred":
|
|
233
|
+
|
|
234
|
+
def relax() -> bool:
|
|
235
|
+
return relax_pred_flat(edges, dist, update_ok, point_to)
|
|
236
|
+
|
|
237
|
+
else:
|
|
238
|
+
|
|
239
|
+
def relax() -> bool:
|
|
240
|
+
return relax_succ_flat(edges, dist, update_ok, point_to)
|
|
241
|
+
|
|
242
|
+
while not found and relax():
|
|
243
|
+
num_iter += 1
|
|
175
244
|
for vtx in find_cycle(digraph, point_to):
|
|
176
245
|
if verify:
|
|
177
246
|
# Safety check - verify the cycle is indeed negative
|
|
178
247
|
assert is_negative(point_to, vtx, dist, get_weight)
|
|
179
248
|
found = True
|
|
180
249
|
yield cycle_list(point_to, vtx)
|
|
250
|
+
if not found and max_iter is not None and num_iter >= max_iter:
|
|
251
|
+
raise RuntimeError(
|
|
252
|
+
f"howard_search did not converge within max_iter={max_iter}"
|
|
253
|
+
)
|
|
@@ -50,11 +50,8 @@ def _run_loop(
|
|
|
50
50
|
if not dist: # empty graph case - return early with no cycle found
|
|
51
51
|
return ratio, []
|
|
52
52
|
|
|
53
|
-
DomainType = type(next(iter(dist.values())))
|
|
54
|
-
|
|
55
|
-
# Define a weight function that calculates distance based on current ratio
|
|
56
53
|
def get_weight(e: Arc) -> Domain:
|
|
57
|
-
return
|
|
54
|
+
return omega.distance(ratio, e)
|
|
58
55
|
|
|
59
56
|
# Initialize min/max ratio and cycle
|
|
60
57
|
ratio_best = ratio
|
|
@@ -46,6 +46,7 @@ from typing import (
|
|
|
46
46
|
Generic,
|
|
47
47
|
Mapping,
|
|
48
48
|
MutableMapping,
|
|
49
|
+
Optional,
|
|
49
50
|
Tuple,
|
|
50
51
|
Union,
|
|
51
52
|
)
|
|
@@ -241,12 +242,16 @@ class NegCycleFinder(Generic[Node, Arc, Domain]):
|
|
|
241
242
|
self,
|
|
242
243
|
dist: MutableMapping[Node, Domain],
|
|
243
244
|
get_weight: Callable[[Arc], Domain],
|
|
245
|
+
max_iter: Optional[int] = None,
|
|
244
246
|
) -> Generator[Cycle, None, None]:
|
|
245
247
|
"""Main algorithm to find negative cycles using Howard's method.
|
|
246
248
|
|
|
247
249
|
Args:
|
|
248
250
|
dist: Initial distance estimates (often initialized to zero)
|
|
249
251
|
get_weight: Function to get edge weights
|
|
252
|
+
max_iter: Optional cap on relaxation rounds; ``None`` (default) is
|
|
253
|
+
unbounded. Exceeding the cap without a negative cycle raises
|
|
254
|
+
``RuntimeError``.
|
|
250
255
|
|
|
251
256
|
Yields:
|
|
252
257
|
Generator[Cycle, None, None]: Each found negative cycle as a list of edges
|
|
@@ -274,5 +279,11 @@ class NegCycleFinder(Generic[Node, Arc, Domain]):
|
|
|
274
279
|
False
|
|
275
280
|
"""
|
|
276
281
|
yield from _howard_search(
|
|
277
|
-
self.digraph,
|
|
282
|
+
self.digraph,
|
|
283
|
+
dist,
|
|
284
|
+
get_weight,
|
|
285
|
+
_always_true,
|
|
286
|
+
self.pred,
|
|
287
|
+
"pred",
|
|
288
|
+
max_iter=max_iter,
|
|
278
289
|
)
|
|
@@ -44,6 +44,7 @@ from typing import (
|
|
|
44
44
|
Generic,
|
|
45
45
|
Mapping,
|
|
46
46
|
MutableMapping,
|
|
47
|
+
Optional,
|
|
47
48
|
Tuple,
|
|
48
49
|
Union,
|
|
49
50
|
)
|
|
@@ -202,6 +203,7 @@ class NegCycleFinderQ(Generic[Node, Arc, Domain]):
|
|
|
202
203
|
dist: MutableMapping[Node, Domain],
|
|
203
204
|
get_weight: Callable[[Arc], Domain],
|
|
204
205
|
update_ok: Callable[[Domain, Domain], bool],
|
|
206
|
+
max_iter: Optional[int] = None,
|
|
205
207
|
) -> Generator[Cycle, None, None]:
|
|
206
208
|
"""Find negative cycles using predecessor-based Howard's algorithm.
|
|
207
209
|
|
|
@@ -209,6 +211,9 @@ class NegCycleFinderQ(Generic[Node, Arc, Domain]):
|
|
|
209
211
|
dist: Initial distance estimates (often zero-initialized)
|
|
210
212
|
get_weight: Function to get weight of an edge
|
|
211
213
|
update_ok: Function to determine if distance updates are allowed
|
|
214
|
+
max_iter: Optional cap on relaxation rounds; ``None`` (default) is
|
|
215
|
+
unbounded. Exceeding the cap without a negative cycle raises
|
|
216
|
+
``RuntimeError``.
|
|
212
217
|
|
|
213
218
|
Yields:
|
|
214
219
|
Generator[Cycle, None, None]: Each negative cycle found as a list of edges
|
|
@@ -237,7 +242,13 @@ class NegCycleFinderQ(Generic[Node, Arc, Domain]):
|
|
|
237
242
|
False
|
|
238
243
|
"""
|
|
239
244
|
yield from _howard_search(
|
|
240
|
-
self.digraph,
|
|
245
|
+
self.digraph,
|
|
246
|
+
dist,
|
|
247
|
+
get_weight,
|
|
248
|
+
update_ok,
|
|
249
|
+
self.pred,
|
|
250
|
+
"pred",
|
|
251
|
+
max_iter=max_iter,
|
|
241
252
|
)
|
|
242
253
|
|
|
243
254
|
def howard_succ(
|
|
@@ -245,6 +256,7 @@ class NegCycleFinderQ(Generic[Node, Arc, Domain]):
|
|
|
245
256
|
dist: MutableMapping[Node, Domain],
|
|
246
257
|
get_weight: Callable[[Arc], Domain],
|
|
247
258
|
update_ok: Callable[[Domain, Domain], bool],
|
|
259
|
+
max_iter: Optional[int] = None,
|
|
248
260
|
) -> Generator[Cycle, None, None]:
|
|
249
261
|
"""Find negative cycles using successor-based Howard's algorithm.
|
|
250
262
|
|
|
@@ -252,9 +264,9 @@ class NegCycleFinderQ(Generic[Node, Arc, Domain]):
|
|
|
252
264
|
dist: Initial distance estimates (often zero-initialized)
|
|
253
265
|
get_weight: Function to get weight of an edge
|
|
254
266
|
update_ok: Function to determine if distance updates are allowed
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
267
|
+
max_iter: Optional cap on relaxation rounds; ``None`` (default) is
|
|
268
|
+
unbounded. Exceeding the cap without a negative cycle raises
|
|
269
|
+
``RuntimeError``.
|
|
258
270
|
|
|
259
271
|
Note:
|
|
260
272
|
Similar to howard_pred but uses successor updates instead of predecessor
|
|
@@ -278,7 +290,14 @@ class NegCycleFinderQ(Generic[Node, Arc, Domain]):
|
|
|
278
290
|
False
|
|
279
291
|
"""
|
|
280
292
|
yield from _howard_search(
|
|
281
|
-
self.digraph,
|
|
293
|
+
self.digraph,
|
|
294
|
+
dist,
|
|
295
|
+
get_weight,
|
|
296
|
+
update_ok,
|
|
297
|
+
self.succ,
|
|
298
|
+
"succ",
|
|
299
|
+
verify=False,
|
|
300
|
+
max_iter=max_iter,
|
|
282
301
|
)
|
|
283
302
|
|
|
284
303
|
def cycle_list(self, handle: Node, point_to: Dict[Node, Tuple[Node, Arc]]) -> Cycle:
|
|
@@ -158,7 +158,9 @@ class TinyDiGraph(DiGraphAdapter):
|
|
|
158
158
|
>>> adj_list = graph.cheat_adjlist_outer_dict()
|
|
159
159
|
>>> list(adj_list.keys())
|
|
160
160
|
[0, 1]
|
|
161
|
-
>>> adj_list[0]
|
|
161
|
+
>>> adj_list[0]
|
|
162
|
+
{}
|
|
163
|
+
>>> adj_list.lst[0] is _UNINIT
|
|
162
164
|
True
|
|
163
165
|
"""
|
|
164
166
|
return _LazyMapAdapter([_UNINIT] * self.num_nodes)
|
|
@@ -238,18 +240,44 @@ class TinyDiGraph(DiGraphAdapter):
|
|
|
238
240
|
return self._succ.lst[n] is _UNINIT
|
|
239
241
|
|
|
240
242
|
def add_edge(self, u_of_edge, v_of_edge, **attr): # type: ignore
|
|
243
|
+
"""Add the edge ``u_of_edge -> v_of_edge`` with optional attributes.
|
|
244
|
+
|
|
245
|
+
Resolves the lazy adjacency slots with direct list access instead of
|
|
246
|
+
routing through ``nx.DiGraph.add_edge``, which performs several
|
|
247
|
+
Python-level ``_LazyMapAdapter`` lookups per edge.
|
|
248
|
+
"""
|
|
241
249
|
u, v = u_of_edge, v_of_edge
|
|
242
|
-
self.
|
|
243
|
-
self.
|
|
244
|
-
|
|
250
|
+
succ = self._succ.lst
|
|
251
|
+
pred = self._pred.lst
|
|
252
|
+
if succ[u] is _UNINIT:
|
|
253
|
+
succ[u] = {}
|
|
254
|
+
pred[u] = {}
|
|
255
|
+
if succ[v] is _UNINIT:
|
|
256
|
+
succ[v] = {}
|
|
257
|
+
pred[v] = {}
|
|
258
|
+
neighbors = succ[u]
|
|
259
|
+
datadict = neighbors.get(v)
|
|
260
|
+
if datadict is None:
|
|
261
|
+
datadict = {}
|
|
262
|
+
neighbors[v] = datadict
|
|
263
|
+
if attr:
|
|
264
|
+
datadict.update(attr)
|
|
265
|
+
pred[v][u] = datadict
|
|
266
|
+
nx._clear_cache(self)
|
|
245
267
|
|
|
246
268
|
def add_edges_from(self, ebunch_to_add, **attr): # type: ignore
|
|
247
269
|
for e in ebunch_to_add:
|
|
248
270
|
ne = len(e)
|
|
249
271
|
if ne == 3:
|
|
250
272
|
u, v, dd = e
|
|
251
|
-
|
|
252
|
-
|
|
273
|
+
if attr and dd:
|
|
274
|
+
self.add_edge(u, v, **{**attr, **dd})
|
|
275
|
+
elif attr:
|
|
276
|
+
self.add_edge(u, v, **attr)
|
|
277
|
+
elif dd:
|
|
278
|
+
self.add_edge(u, v, **dd)
|
|
279
|
+
else:
|
|
280
|
+
self.add_edge(u, v)
|
|
253
281
|
elif ne == 2:
|
|
254
282
|
u, v = e
|
|
255
283
|
if attr:
|
|
@@ -19,11 +19,13 @@ requirements.txt
|
|
|
19
19
|
setup.cfg
|
|
20
20
|
setup.py
|
|
21
21
|
tox.ini
|
|
22
|
+
tunable_parameter.md
|
|
22
23
|
.github/workflows/jekyll-gh-pages.yml
|
|
23
24
|
.github/workflows/multi-platforms.yml
|
|
24
25
|
.github/workflows/python-app.yml
|
|
25
26
|
.github/workflows/python-publish.yml
|
|
26
27
|
.vscode/settings.json
|
|
28
|
+
benchmarks/test_benchmarks.py
|
|
27
29
|
docs/Makefile
|
|
28
30
|
docs/authors.md
|
|
29
31
|
docs/changelog.md
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"GEMINI.md",
|
|
18
18
|
"LICENSE.txt",
|
|
19
19
|
"README.md",
|
|
20
|
+
"benchmarks/test_benchmarks.py",
|
|
20
21
|
"coverage.json",
|
|
21
22
|
"docs/Makefile",
|
|
22
23
|
"docs/_static/.gitignore",
|
|
@@ -86,6 +87,7 @@
|
|
|
86
87
|
"tests/test_timing_example_howard.py",
|
|
87
88
|
"tests/test_tiny_digraph.py",
|
|
88
89
|
"tests/test_tiny_digraph_property.py",
|
|
89
|
-
"tox.ini"
|
|
90
|
+
"tox.ini",
|
|
91
|
+
"tunable_parameter.md"
|
|
90
92
|
]
|
|
91
93
|
}
|
|
@@ -3,6 +3,7 @@ from __future__ import print_function
|
|
|
3
3
|
|
|
4
4
|
from typing import Any, Callable, Dict, Union
|
|
5
5
|
|
|
6
|
+
import pytest
|
|
6
7
|
from mywheel.map_adapter import MapAdapter
|
|
7
8
|
|
|
8
9
|
from digraphx.neg_cycle import NegCycleFinder
|
|
@@ -112,3 +113,31 @@ def test_neg_cycle_is_negative_edge_case() -> None:
|
|
|
112
113
|
|
|
113
114
|
# This should return False as the triangle inequality is not violated
|
|
114
115
|
assert not finder.is_negative(0, dist, lambda edge: edge.get("weight", 1))
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_howard_max_iter_default_unbounded() -> None:
|
|
119
|
+
digraph: DiGraphAdapter = DiGraphAdapter()
|
|
120
|
+
digraph.add_edge(0, 1, weight=1)
|
|
121
|
+
dist: Dict[int, int] = {0: 0, 1: 5}
|
|
122
|
+
finder: NegCycleFinder[Any, Any, Any] = NegCycleFinder(digraph)
|
|
123
|
+
assert list(finder.howard(dist, lambda edge: edge.get("weight", 1))) == []
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_howard_max_iter_finds_cycle() -> None:
|
|
127
|
+
digraph: DiGraphAdapter = DiGraphAdapter()
|
|
128
|
+
digraph.add_edge(0, 1, weight=1)
|
|
129
|
+
digraph.add_edge(1, 0, weight=-3)
|
|
130
|
+
dist: Dict[int, int] = {0: 0, 1: 0}
|
|
131
|
+
finder: NegCycleFinder[Any, Any, Any] = NegCycleFinder(digraph)
|
|
132
|
+
cycles = list(finder.howard(dist, lambda edge: edge.get("weight", 1), max_iter=100))
|
|
133
|
+
assert len(cycles) >= 1
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def test_howard_max_iter_raises_when_exceeded() -> None:
|
|
137
|
+
digraph: DiGraphAdapter = DiGraphAdapter()
|
|
138
|
+
digraph.add_edge(0, 1, weight=1)
|
|
139
|
+
dist: Dict[int, int] = {0: 0, 1: 5}
|
|
140
|
+
finder: NegCycleFinder[Any, Any, Any] = NegCycleFinder(digraph)
|
|
141
|
+
with pytest.raises(RuntimeError):
|
|
142
|
+
for _ in finder.howard(dist, lambda edge: edge.get("weight", 1), max_iter=1):
|
|
143
|
+
pass
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Tunable Parameters in `digraphx`
|
|
2
|
+
|
|
3
|
+
A complete reference of every user-tunable parameter of the algorithms in this
|
|
4
|
+
project, together with its default value and source location.
|
|
5
|
+
|
|
6
|
+
## Key finding
|
|
7
|
+
|
|
8
|
+
`digraphx` has **no `Options` / config dataclass**. There are zero `@dataclass`
|
|
9
|
+
or `NamedTuple` definitions in the package. Every tunable is a plain
|
|
10
|
+
function/method parameter or an injected callback. The dominant policy knob is
|
|
11
|
+
the `update_ok` predicate that gates Bellman-Ford relaxation.
|
|
12
|
+
|
|
13
|
+
This report separates the categories explicitly so it is clear what a caller
|
|
14
|
+
can actually adjust.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 1. Negative-cycle finders
|
|
19
|
+
|
|
20
|
+
Files: `src/digraphx/neg_cycle.py`, `src/digraphx/neg_cycle_q.py`,
|
|
21
|
+
internal `src/digraphx/_cycle_base.py`.
|
|
22
|
+
|
|
23
|
+
| API | Parameters | Default |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| `NegCycleFinder(digraph)` | `digraph` | required |
|
|
26
|
+
| `NegCycleFinder.howard(dist, get_weight, max_iter=None)` | `max_iter: int \| None` | `None` (unbounded) |
|
|
27
|
+
| `NegCycleFinderQ(digraph)` | `digraph` | required |
|
|
28
|
+
| `NegCycleFinderQ.relax_pred(dist, get_weight, update_ok)` | `update_ok` (predicate) | required |
|
|
29
|
+
| `NegCycleFinderQ.relax_succ(dist, get_weight, update_ok)` | `update_ok` | required |
|
|
30
|
+
| `NegCycleFinderQ.howard_pred(dist, get_weight, update_ok, max_iter=None)` | `update_ok`; `max_iter` | required; `None` |
|
|
31
|
+
| `NegCycleFinderQ.howard_succ(dist, get_weight, update_ok, max_iter=None)` | `update_ok`; `max_iter` | required; `None` |
|
|
32
|
+
| `howard_search(..., direction, verify=True, max_iter=None)` (internal) | `verify: bool`; `max_iter` | `True`; `None` |
|
|
33
|
+
|
|
34
|
+
Notes:
|
|
35
|
+
|
|
36
|
+
- `update_ok(old, new) -> bool` is the relaxation-gate callback. When
|
|
37
|
+
`NegCycleFinder` omits it, the gate is `_always_true`
|
|
38
|
+
(`src/digraphx/_cycle_base.py:36-38`).
|
|
39
|
+
- `NegCycleFinderQ.howard_succ` hard-wires `verify=False`; `howard_pred` uses
|
|
40
|
+
`verify=True` (`src/digraphx/neg_cycle_q.py:281` and `:239-241`).
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 2. Parametric solvers
|
|
45
|
+
|
|
46
|
+
Files: `src/digraphx/parametric.py`, `src/digraphx/min_parametric_q.py`,
|
|
47
|
+
internal `src/digraphx/_parametric_base.py`.
|
|
48
|
+
|
|
49
|
+
| API | Parameters | Default |
|
|
50
|
+
| --- | --- | --- |
|
|
51
|
+
| `MaxParametricSolver(digraph, omega)` | `omega` (strategy) | required |
|
|
52
|
+
| `MaxParametricSolver.run(dist, ratio)` | — | no tunables (internally `minimize=True`) |
|
|
53
|
+
| `MinParametricSolver(digraph, omega)` | `omega` | required |
|
|
54
|
+
| `MinParametricSolver.run(dist, ratio, update_ok, pick_one_only=False)` | `update_ok`; `pick_one_only: bool` | required; `False` |
|
|
55
|
+
| `_run_loop(..., *, minimize, update_ok=..., pick_one_only=False, alternate_direction=False)` (internal) | `update_ok`, `pick_one_only`, `alternate_direction` | `lambda old, new: True`; `False`; `False` |
|
|
56
|
+
|
|
57
|
+
- `MinParametricSolver.run` is the public surface
|
|
58
|
+
(`src/digraphx/min_parametric_q.py:125-131`).
|
|
59
|
+
- `alternate_direction=True` is fixed internally for the constrained solver.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 3. Minimum cycle ratio
|
|
64
|
+
|
|
65
|
+
File: `src/digraphx/min_cycle_ratio.py`.
|
|
66
|
+
|
|
67
|
+
| API | Parameters | Default |
|
|
68
|
+
| --- | --- | --- |
|
|
69
|
+
| `MinCycleRatioSolver(digraph)` | `digraph` | required |
|
|
70
|
+
| `MinCycleRatioSolver.run(dist, ratio0)` | `ratio0` (initial ratio) | required |
|
|
71
|
+
| `CycleRatioAPI(digraph, result_type)` | `result_type` (`Fraction` or `float`) | required (selects numeric precision) |
|
|
72
|
+
| `set_default(digraph, weight, value)` | `weight: str`, `value` | required |
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 4. Min-cost flow (cycle-canceling)
|
|
77
|
+
|
|
78
|
+
File: `src/digraphx/mcf.py`.
|
|
79
|
+
|
|
80
|
+
| API | Parameters | Default |
|
|
81
|
+
| --- | --- | --- |
|
|
82
|
+
| `cycle_canceling_mcf(g, demands, sink=None)` | `sink` | `None` |
|
|
83
|
+
| `VertexFilter(sink)` | `sink` | required |
|
|
84
|
+
|
|
85
|
+
- When `sink` is provided, the vertex-disjoint constraint is enabled; when
|
|
86
|
+
`None`, no constraint is enforced.
|
|
87
|
+
- Edge-data defaults consumed internally: `capacity` defaults to `inf`,
|
|
88
|
+
`weight` defaults to `0`; the bottleneck is truncated via `int(...)`
|
|
89
|
+
(`src/digraphx/mcf.py:61-62`, `:239`, `:304`).
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## 5. Graph containers
|
|
94
|
+
|
|
95
|
+
Files: `src/digraphx/tiny_digraph.py`, `src/digraphx/csr_digraph.py`.
|
|
96
|
+
|
|
97
|
+
| API | Parameters | Default |
|
|
98
|
+
| --- | --- | --- |
|
|
99
|
+
| `TinyDiGraph.init_nodes(num_nodes)` | `num_nodes: int` | required |
|
|
100
|
+
| `TinyDiGraph.add_edge(u, v, **attr)` | free-form attributes | — |
|
|
101
|
+
| `CSRDiGraph.init_nodes(num_nodes)` | `num_nodes: int` | required |
|
|
102
|
+
| `CSRDiGraph.add_edge(u, v, **attr)` | free-form attributes | — |
|
|
103
|
+
| `CSRDiGraph.freeze()` | — | — |
|
|
104
|
+
| `DiGraphAdapter.get_edge_data(u, v, default=None)` | `default` | `None` |
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 6. Repository utilities (experiments, not core library)
|
|
109
|
+
|
|
110
|
+
File: `experimental/spare_tsv.py`.
|
|
111
|
+
|
|
112
|
+
| Function | Parameters | Default |
|
|
113
|
+
| --- | --- | --- |
|
|
114
|
+
| `formGraph(T, pos, mu, eta, seed=None)` | `seed` | `None` |
|
|
115
|
+
| `vdc(n, base=2)` | `base` | `2` |
|
|
116
|
+
| `vdcorput(n, base=2)` | `base` | `2` |
|
|
117
|
+
| `vdcorput_iter(n, base=2)` | `base` | `2` |
|
|
118
|
+
| `showPaths(gra, pos, N, edgeProbs=1.0, path=None, visibleNodes=None, guards=None)` | `edgeProbs`, `path`, `visibleNodes`, `guards` | `1.0`, `None`, `None`, `None` |
|
|
119
|
+
| `setup_network_flow(gra, pos, primal_count, capacity)` | `capacity` | required |
|
|
120
|
+
| `solve_network_flow(gra, sink_node)` | `sink_node` | required |
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 7. Fixed values in tests and benchmarks (not library defaults)
|
|
125
|
+
|
|
126
|
+
| Source | Constant | Value |
|
|
127
|
+
| --- | --- | --- |
|
|
128
|
+
| `benchmarks/test_benchmarks.py:21-23` | `SEED`, `SIZES`, `MCR_SIZES` | `1234`, `[200, 1000]`, `[50, 100]` |
|
|
129
|
+
| `benchmarks/test_benchmarks.py:53-61` | node degree | `5` / `6` |
|
|
130
|
+
| `tests/test_*howard*.py:14,64,107` | test-local Howard helper `max_iter` | `2000` |
|
|
131
|
+
| `experiments/experi.py:13-23` and `experi-descent*.py` | `N`, `M`, `r`, `mu`, `eta`, `seed`, `xbase`, `ybase` | `155`, `40`, `4`, `0.12`, `1.6`, `5`, `2`, `3` |
|
|
132
|
+
| `docs/examples/plot_*.py` | `seed` | `42` |
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Summary of genuine tunables
|
|
137
|
+
|
|
138
|
+
| Tunable | Default | Location |
|
|
139
|
+
| --- | --- | --- |
|
|
140
|
+
| `update_ok` predicate (relaxation gate) | `lambda old, new: True` | `src/digraphx/_cycle_base.py:37`, `src/digraphx/_parametric_base.py:37` |
|
|
141
|
+
| `howard`/`howard_pred`/`howard_succ` `max_iter` | `None` (unbounded; raises `RuntimeError` if set and exceeded) | `src/digraphx/neg_cycle.py`, `src/digraphx/neg_cycle_q.py` |
|
|
142
|
+
| `MinParametricSolver.run(..., pick_one_only)` | `False` | `src/digraphx/min_parametric_q.py:130` |
|
|
143
|
+
| `cycle_canceling_mcf(..., sink)` | `None` | `src/digraphx/mcf.py:254` |
|
|
144
|
+
| `CycleRatioAPI(..., result_type)` | required (`Fraction` / `float`) | `src/digraphx/min_cycle_ratio.py:113` |
|
|
145
|
+
| `howard_search(..., verify)` (internal) | `True` | `src/digraphx/_cycle_base.py:210` |
|
|
146
|
+
| `_run_loop(..., alternate_direction)` (internal) | `False` | `src/digraphx/_parametric_base.py:39` |
|
|
147
|
+
| `formGraph(..., seed)` | `None` | `experimental/spare_tsv.py:34` |
|
|
148
|
+
| `vdc` / `vdcorput` / `vdcorput_iter(..., base)` | `2` | `experimental/spare_tsv.py:10,20,25` |
|
|
149
|
+
| `showPaths` overlay options | `edgeProbs=1.0`, `path=None`, `visibleNodes=None`, `guards=None` | `experimental/spare_tsv.py:54` |
|
|
150
|
+
|
|
151
|
+
Everything else is problem data (graph, distances, ratio, demands) or a
|
|
152
|
+
hardcoded constant (`capacity=inf`, `weight=0`, integer bottleneck,
|
|
153
|
+
`verify=False` on `howard_succ`).
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|