digraphx 0.1__tar.gz → 0.2__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.2/GEMINI.md +65 -0
- {digraphx-0.1/src/digraphx.egg-info → digraphx-0.2}/PKG-INFO +1 -1
- {digraphx-0.1 → digraphx-0.2}/src/digraphx/min_cycle_ratio.py +35 -38
- {digraphx-0.1 → digraphx-0.2}/src/digraphx/min_parmetric_q.py +165 -150
- {digraphx-0.1 → digraphx-0.2}/src/digraphx/neg_cycle.py +79 -78
- {digraphx-0.1 → digraphx-0.2}/src/digraphx/neg_cycle_q.py +107 -123
- {digraphx-0.1 → digraphx-0.2}/src/digraphx/parametric.py +30 -6
- {digraphx-0.1 → digraphx-0.2}/src/digraphx/tiny_digraph.py +54 -18
- {digraphx-0.1 → digraphx-0.2/src/digraphx.egg-info}/PKG-INFO +1 -1
- {digraphx-0.1 → digraphx-0.2}/src/digraphx.egg-info/SOURCES.txt +3 -1
- {digraphx-0.1 → digraphx-0.2}/tests/test_cycle_ratio.py +24 -0
- digraphx-0.2/tests/test_min_parmetric_q.py +30 -0
- {digraphx-0.1 → digraphx-0.2}/tests/test_neg_cycle.py +37 -1
- {digraphx-0.1 → digraphx-0.2}/tests/test_neg_cycle_q.py +58 -1
- digraphx-0.2/tests/test_tiny_digraph.py +48 -0
- digraphx-0.1/src/digraphx/max_cycle_ratio.tpy +0 -134
- {digraphx-0.1 → digraphx-0.2}/.coveragerc +0 -0
- {digraphx-0.1 → digraphx-0.2}/.github/workflows/ci.bak +0 -0
- {digraphx-0.1 → digraphx-0.2}/.github/workflows/jekyll-gh-pages.yml +0 -0
- {digraphx-0.1 → digraphx-0.2}/.github/workflows/multi-platforms.yml +0 -0
- {digraphx-0.1 → digraphx-0.2}/.github/workflows/python-app.yml +0 -0
- {digraphx-0.1 → digraphx-0.2}/.github/workflows/python-publish.yml +0 -0
- {digraphx-0.1 → digraphx-0.2}/.gitignore +0 -0
- {digraphx-0.1 → digraphx-0.2}/.isort.cfg +0 -0
- {digraphx-0.1 → digraphx-0.2}/.pre-commit-config.yaml +0 -0
- {digraphx-0.1 → digraphx-0.2}/.readthedocs.yml +0 -0
- {digraphx-0.1 → digraphx-0.2}/AUTHORS.md +0 -0
- {digraphx-0.1 → digraphx-0.2}/CHANGELOG.md +0 -0
- {digraphx-0.1 → digraphx-0.2}/CONTRIBUTING.md +0 -0
- {digraphx-0.1 → digraphx-0.2}/LICENSE +0 -0
- {digraphx-0.1 → digraphx-0.2}/LICENSE.txt +0 -0
- {digraphx-0.1 → digraphx-0.2}/README.md +0 -0
- {digraphx-0.1 → digraphx-0.2}/docs/Makefile +0 -0
- {digraphx-0.1 → digraphx-0.2}/docs/_static/.gitignore +0 -0
- {digraphx-0.1 → digraphx-0.2}/docs/authors.md +0 -0
- {digraphx-0.1 → digraphx-0.2}/docs/changelog.md +0 -0
- {digraphx-0.1 → digraphx-0.2}/docs/conf.py +0 -0
- {digraphx-0.1 → digraphx-0.2}/docs/contributing.md +0 -0
- {digraphx-0.1 → digraphx-0.2}/docs/index.md +0 -0
- {digraphx-0.1 → digraphx-0.2}/docs/license.md +0 -0
- {digraphx-0.1 → digraphx-0.2}/docs/readme.md +0 -0
- {digraphx-0.1 → digraphx-0.2}/docs/requirements.txt +0 -0
- {digraphx-0.1 → digraphx-0.2}/environment.yml +0 -0
- {digraphx-0.1 → digraphx-0.2}/experiments/plot_node_colormap.ipynb +0 -0
- {digraphx-0.1 → digraphx-0.2}/experiments/plot_node_colormap.py +0 -0
- {digraphx-0.1 → digraphx-0.2}/mypy.ini +0 -0
- {digraphx-0.1 → digraphx-0.2}/note.md +0 -0
- {digraphx-0.1 → digraphx-0.2}/pyproject.toml +0 -0
- {digraphx-0.1 → digraphx-0.2}/requirements/README.md +0 -0
- {digraphx-0.1 → digraphx-0.2}/requirements/default.txt +0 -0
- {digraphx-0.1 → digraphx-0.2}/requirements/doc.txt +0 -0
- {digraphx-0.1 → digraphx-0.2}/requirements/test.txt +0 -0
- {digraphx-0.1 → digraphx-0.2}/requirements.txt +0 -0
- {digraphx-0.1 → digraphx-0.2}/setup.cfg +0 -0
- {digraphx-0.1 → digraphx-0.2}/setup.py +0 -0
- {digraphx-0.1 → digraphx-0.2}/src/digraphx/__init__.py +0 -0
- {digraphx-0.1 → digraphx-0.2}/src/digraphx/skeleton.py +0 -0
- {digraphx-0.1 → digraphx-0.2}/src/digraphx.egg-info/dependency_links.txt +0 -0
- {digraphx-0.1 → digraphx-0.2}/src/digraphx.egg-info/not-zip-safe +0 -0
- {digraphx-0.1 → digraphx-0.2}/src/digraphx.egg-info/requires.txt +0 -0
- {digraphx-0.1 → digraphx-0.2}/src/digraphx.egg-info/top_level.txt +0 -0
- {digraphx-0.1 → digraphx-0.2}/tests/__init__.py +0 -0
- {digraphx-0.1 → digraphx-0.2}/tests/conftest.py +0 -0
- {digraphx-0.1 → digraphx-0.2}/tests/test_skeleton.py +0 -0
- {digraphx-0.1 → digraphx-0.2}/tests/travis_install.sh +0 -0
- {digraphx-0.1 → digraphx-0.2}/tox.ini +0 -0
digraphx-0.2/GEMINI.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Gemini Code Assistant Context
|
|
2
|
+
|
|
3
|
+
## Project Overview
|
|
4
|
+
|
|
5
|
+
This project, `digraphx`, is a Python library for network optimization on directed graphs. It provides algorithms for finding minimum cycle ratios, solving parametric network problems, and detecting negative cycles using Howard's algorithm. The library is built on top of `networkx` but includes a custom `TinyDiGraph` data structure for improved memory efficiency with large graphs.
|
|
6
|
+
|
|
7
|
+
The project is structured as a standard Python library using a `src` layout and is configured with `setup.cfg` and `pyproject.toml`.
|
|
8
|
+
|
|
9
|
+
### Key Technologies
|
|
10
|
+
|
|
11
|
+
* **Language:** Python
|
|
12
|
+
* **Core Library:** `networkx`
|
|
13
|
+
* **Testing:** `pytest`, `pytest-cov`, `tox`
|
|
14
|
+
* **Code Style:** `flake8`, `pre-commit`
|
|
15
|
+
|
|
16
|
+
### Core Modules
|
|
17
|
+
|
|
18
|
+
* `min_cycle_ratio.py`: Implements the Minimum Cycle Ratio (MCR) solver.
|
|
19
|
+
* `parametric.py` & `min_parmetric_q.py`: Implements solvers for parametric network problems.
|
|
20
|
+
* `neg_cycle.py` & `neg_cycle_q.py`: Implements negative cycle detection using Howard's algorithm.
|
|
21
|
+
* `tiny_digraph.py`: Defines a memory-efficient `TinyDiGraph` data structure.
|
|
22
|
+
|
|
23
|
+
## Building and Running
|
|
24
|
+
|
|
25
|
+
This is a library, so there is no main application to run. However, you can install it and run tests.
|
|
26
|
+
|
|
27
|
+
### Installation
|
|
28
|
+
|
|
29
|
+
To install the project in editable mode, use the following command:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install -e .
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Running Tests
|
|
36
|
+
|
|
37
|
+
The project uses `pytest` for testing. To run the tests, use the following command:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pytest
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
You can also use `tox` to run tests in different Python environments:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
tox
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Development Conventions
|
|
50
|
+
|
|
51
|
+
### Code Style
|
|
52
|
+
|
|
53
|
+
The project uses `flake8` for linting and `pre-commit` to enforce code style. Before committing any changes, make sure to run `pre-commit`:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pre-commit run --all-files
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Testing
|
|
60
|
+
|
|
61
|
+
All new features should be accompanied by tests. The tests are located in the `tests` directory and follow the standard `pytest` conventions.
|
|
62
|
+
|
|
63
|
+
### Contribution Guidelines
|
|
64
|
+
|
|
65
|
+
The `CONTRIBUTING.md` file provides guidelines for contributing to the project. Please review it before making any contributions.
|
|
@@ -26,7 +26,9 @@ from typing import Generic, Mapping, MutableMapping, Tuple, TypeVar
|
|
|
26
26
|
from .neg_cycle import Cycle, Domain, Edge, Node
|
|
27
27
|
from .parametric import MaxParametricSolver, ParametricAPI
|
|
28
28
|
|
|
29
|
+
# Define type variables for generic programming
|
|
29
30
|
Ratio = TypeVar("Ratio", Fraction, float)
|
|
31
|
+
# Define graph types for type hints
|
|
30
32
|
Graph = Mapping[Node, Mapping[Node, Mapping[str, Domain]]]
|
|
31
33
|
GraphMut = MutableMapping[Node, MutableMapping[Node, MutableMapping[str, Domain]]]
|
|
32
34
|
|
|
@@ -34,6 +36,8 @@ GraphMut = MutableMapping[Node, MutableMapping[Node, MutableMapping[str, Domain]
|
|
|
34
36
|
def set_default(digraph: GraphMut, weight: str, value: Domain) -> None:
|
|
35
37
|
"""
|
|
36
38
|
This function sets a default value for a specified weight in a graph.
|
|
39
|
+
It iterates through all edges in the graph and sets the specified weight to the given value
|
|
40
|
+
if it's not already present in the edge attributes.
|
|
37
41
|
|
|
38
42
|
:param digraph: The parameter `digraph` is of type `GraphMut`, which is likely a mutable graph data
|
|
39
43
|
structure. It represents a graph where each node has a dictionary of neighbors and their
|
|
@@ -55,27 +59,25 @@ def set_default(digraph: GraphMut, weight: str, value: Domain) -> None:
|
|
|
55
59
|
e[weight] = value
|
|
56
60
|
|
|
57
61
|
|
|
58
|
-
# The `CycleRatioAPI` class is a parametric API that calculates the ratio of a cycle based on the cost
|
|
59
|
-
# and time of its edges.
|
|
60
62
|
class CycleRatioAPI(ParametricAPI[Node, MutableMapping[str, Domain], Ratio]):
|
|
63
|
+
"""
|
|
64
|
+
This class implements the parametric API for cycle ratio calculations.
|
|
65
|
+
It provides methods to compute distances based on a given ratio and to calculate
|
|
66
|
+
the actual ratio for a given cycle.
|
|
67
|
+
"""
|
|
68
|
+
|
|
61
69
|
def __init__(
|
|
62
70
|
self,
|
|
63
71
|
digraph: Mapping[Node, Mapping[Node, Mapping[str, Domain]]],
|
|
64
72
|
result_type: type,
|
|
65
73
|
) -> None:
|
|
66
74
|
"""
|
|
67
|
-
|
|
68
|
-
variables.
|
|
69
|
-
|
|
70
|
-
:param digraph: A mapping of nodes to a mapping of nodes to a mapping of strings to domains. It
|
|
71
|
-
represents a graph structure where each node is connected to other nodes through edges, and each
|
|
72
|
-
edge has associated attributes represented by strings and domains
|
|
75
|
+
Initialize the CycleRatioAPI with a graph and result type.
|
|
73
76
|
|
|
77
|
+
:param digraph: The graph structure where nodes map to neighbors and edge attributes
|
|
74
78
|
:type digraph: Mapping[Node, Mapping[Node, Mapping[str, Domain]]]
|
|
75
79
|
|
|
76
|
-
:param result_type: The
|
|
77
|
-
can be any valid Python type, such as `int`, `str`, `list`, etc
|
|
78
|
-
|
|
80
|
+
:param result_type: The type to use for calculations (Fraction or float)
|
|
79
81
|
:type result_type: type
|
|
80
82
|
"""
|
|
81
83
|
self.digraph: Mapping[Node, Mapping[Node, Mapping[str, Domain]]] = digraph
|
|
@@ -83,41 +85,36 @@ class CycleRatioAPI(ParametricAPI[Node, MutableMapping[str, Domain], Ratio]):
|
|
|
83
85
|
|
|
84
86
|
def distance(self, ratio: Ratio, edge: MutableMapping[str, Domain]) -> Ratio:
|
|
85
87
|
"""
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
:param ratio: The ratio parameter is of type Ratio. It is used in the calculation of the return value
|
|
88
|
+
Calculate the parametric distance for an edge given the current ratio.
|
|
89
|
+
The distance formula is: cost - ratio * time
|
|
89
90
|
|
|
91
|
+
:param ratio: The current ratio value being tested
|
|
90
92
|
:type ratio: Ratio
|
|
91
93
|
|
|
92
|
-
:param edge: The
|
|
93
|
-
information about a specific edge in a graph. It has two keys: "cost" and "time". The value
|
|
94
|
-
associated with the "cost" key represents the cost of traversing the edge, while the value
|
|
95
|
-
associated with
|
|
96
|
-
|
|
94
|
+
:param edge: The edge with 'cost' and 'time' attributes
|
|
97
95
|
:type edge: MutableMapping[str, Domain]
|
|
98
96
|
|
|
99
|
-
:return:
|
|
97
|
+
:return: The calculated distance value
|
|
98
|
+
:rtype: Ratio
|
|
100
99
|
"""
|
|
101
100
|
return self.result_type(edge["cost"]) - ratio * edge["time"]
|
|
102
101
|
|
|
103
102
|
def zero_cancel(self, cycle: Cycle) -> Ratio:
|
|
104
103
|
"""
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
:param cycle: The `cycle` parameter is of type `Cycle`. It represents a cycle, which is a sequence
|
|
108
|
-
of edges in a graph that starts and ends at the same vertex. Each edge in the cycle is a dictionary
|
|
109
|
-
with keys "cost" and "time", representing the cost and time associated with that edge
|
|
104
|
+
Calculate the actual ratio for a given cycle by summing all costs and times.
|
|
105
|
+
The ratio is computed as: total_cost / total_time
|
|
110
106
|
|
|
107
|
+
:param cycle: A sequence of edges forming a cycle
|
|
111
108
|
:type cycle: Cycle
|
|
112
109
|
|
|
113
|
-
:return:
|
|
110
|
+
:return: The calculated cycle ratio
|
|
111
|
+
:rtype: Ratio
|
|
114
112
|
"""
|
|
115
113
|
total_cost = sum(edge["cost"] for edge in cycle)
|
|
116
114
|
total_time = sum(edge["time"] for edge in cycle)
|
|
117
115
|
return self.result_type(total_cost) / total_time
|
|
118
116
|
|
|
119
117
|
|
|
120
|
-
# The `MinCycleRatioSolver` class is a solver for the minimum cycle ratio problem in directed graphs.
|
|
121
118
|
class MinCycleRatioSolver(Generic[Node, Edge, Ratio]):
|
|
122
119
|
"""Minimum Cycle Ratio Solver
|
|
123
120
|
|
|
@@ -143,30 +140,30 @@ class MinCycleRatioSolver(Generic[Node, Edge, Ratio]):
|
|
|
143
140
|
|
|
144
141
|
def __init__(self, digraph: Graph) -> None:
|
|
145
142
|
"""
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
:param digraph: The `digraph` parameter is a mapping of nodes to a mapping of nodes to any type of value. It
|
|
149
|
-
represents a graph where each node is associated with a set of neighboring nodes and their
|
|
150
|
-
corresponding values
|
|
143
|
+
Initialize the solver with the graph to analyze.
|
|
151
144
|
|
|
145
|
+
:param digraph: The graph structure where nodes map to neighbors and edge attributes
|
|
152
146
|
:type digraph: Graph
|
|
153
147
|
"""
|
|
154
148
|
self.digraph: Graph = digraph
|
|
155
149
|
|
|
156
150
|
def run(self, dist: MutableMapping[Node, Domain], r0: Ratio) -> Tuple[Ratio, Cycle]:
|
|
157
151
|
"""
|
|
158
|
-
|
|
152
|
+
Run the minimum cycle ratio solver algorithm.
|
|
159
153
|
|
|
160
|
-
|
|
161
|
-
|
|
154
|
+
The algorithm works by:
|
|
155
|
+
1. Creating a CycleRatioAPI instance with the graph and ratio type
|
|
156
|
+
2. Using a MaxParametricSolver to find the optimal ratio
|
|
157
|
+
3. Returning both the optimal ratio and the corresponding cycle
|
|
162
158
|
|
|
159
|
+
:param dist: Initial distance labels for nodes
|
|
163
160
|
:type dist: MutableMapping[Node, Domain]
|
|
164
161
|
|
|
165
|
-
:param r0:
|
|
166
|
-
|
|
162
|
+
:param r0: Initial ratio value to start the search
|
|
167
163
|
:type r0: Ratio
|
|
168
164
|
|
|
169
|
-
:return:
|
|
165
|
+
:return: A tuple containing the optimal ratio and the cycle that achieves it
|
|
166
|
+
:rtype: Tuple[Ratio, Cycle]
|
|
170
167
|
"""
|
|
171
168
|
omega = CycleRatioAPI(self.digraph, type(r0))
|
|
172
169
|
solver = MaxParametricSolver(self.digraph, omega)
|
|
@@ -1,150 +1,165 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Min Parametric Solver
|
|
3
|
-
|
|
4
|
-
This code defines a system for solving a specific type of network optimization problem called a "minimum parametric problem." The purpose of this code is to find the smallest possible value for a parameter (called a ratio) that satisfies certain conditions in a graph-like structure.
|
|
5
|
-
|
|
6
|
-
The code takes as input a graph (represented as a mapping of nodes and edges), an initial set of distances between nodes, and a starting ratio. It then works to find the smallest ratio that meets the problem's constraints.
|
|
7
|
-
|
|
8
|
-
The main output of
|
|
9
|
-
|
|
10
|
-
To achieve its purpose, the code uses an algorithm that repeatedly searches for cycles in the graph that could potentially lower the ratio. It does this by using a "negative cycle finder" (NCF) which looks for cycles where the sum of the distances (adjusted by the current ratio) is negative. If such a cycle is found, it means the ratio can be lowered further.
|
|
11
|
-
|
|
12
|
-
The main logic flow involves a loop that alternates between searching for cycles and updating the ratio. Each time a cycle is found that allows for a lower ratio, the ratio is updated. This process continues until no more improvements can be made - at this point, the minimum ratio has been found.
|
|
13
|
-
|
|
14
|
-
An important part of the algorithm is the ability to switch between searching for cycles in the forward direction (successor nodes) and the backward direction (predecessor nodes). This helps to explore the graph more thoroughly and find the best possible solution.
|
|
15
|
-
|
|
16
|
-
The code is designed to be flexible, allowing for different types of numbers (integers, fractions, or floating-point numbers) to be used for distances and ratios. It also includes an abstract base class (MinParametricAPI) that defines the interface for calculating distances and handling cycles, allowing for different implementations of these operations.
|
|
17
|
-
|
|
18
|
-
Overall, this code provides a framework for solving complex network optimization problems, particularly those where a single parameter needs to be minimized while satisfying constraints across the entire network.
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
from abc import abstractmethod
|
|
22
|
-
from fractions import Fraction
|
|
23
|
-
from typing import Generic, Mapping, MutableMapping, Tuple, TypeVar
|
|
24
|
-
|
|
25
|
-
from .neg_cycle_q import Cycle, Edge, NegCycleFinder, Node
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
:param
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"""
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"""
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
:
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
:
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
ratio
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
:param
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
:
|
|
113
|
-
|
|
114
|
-
:param update_ok:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
:
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
1
|
+
"""
|
|
2
|
+
Min Parametric Solver
|
|
3
|
+
|
|
4
|
+
This code defines a system for solving a specific type of network optimization problem called a "minimum parametric problem." The purpose of this code is to find the smallest possible value for a parameter (called a ratio) that satisfies certain conditions in a graph-like structure.
|
|
5
|
+
|
|
6
|
+
The code takes as input a graph (represented as a mapping of nodes and edges), an initial set of distances between nodes, and a starting ratio. It then works to find the smallest ratio that meets the problem's constraints.
|
|
7
|
+
|
|
8
|
+
The main output of the code is a tuple containing two things: the final (minimum) ratio found, and a cycle in the graph that corresponds to this ratio.
|
|
9
|
+
|
|
10
|
+
To achieve its purpose, the code uses an algorithm that repeatedly searches for cycles in the graph that could potentially lower the ratio. It does this by using a "negative cycle finder" (NCF) which looks for cycles where the sum of the distances (adjusted by the current ratio) is negative. If such a cycle is found, it means the ratio can be lowered further.
|
|
11
|
+
|
|
12
|
+
The main logic flow involves a loop that alternates between searching for cycles and updating the ratio. Each time a cycle is found that allows for a lower ratio, the ratio is updated. This process continues until no more improvements can be made - at this point, the minimum ratio has been found.
|
|
13
|
+
|
|
14
|
+
An important part of the algorithm is the ability to switch between searching for cycles in the forward direction (successor nodes) and the backward direction (predecessor nodes). This helps to explore the graph more thoroughly and find the best possible solution.
|
|
15
|
+
|
|
16
|
+
The code is designed to be flexible, allowing for different types of numbers (integers, fractions, or floating-point numbers) to be used for distances and ratios. It also includes an abstract base class (MinParametricAPI) that defines the interface for calculating distances and handling cycles, allowing for different implementations of these operations.
|
|
17
|
+
|
|
18
|
+
Overall, this code provides a framework for solving complex network optimization problems, particularly those where a single parameter needs to be minimized while satisfying constraints across the entire network.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from abc import abstractmethod
|
|
22
|
+
from fractions import Fraction
|
|
23
|
+
from typing import Callable, Generic, Mapping, MutableMapping, Tuple, TypeVar
|
|
24
|
+
|
|
25
|
+
from .neg_cycle_q import Cycle, Edge, NegCycleFinder, Node
|
|
26
|
+
|
|
27
|
+
# Define type variables for domain (numeric types) and ratio (fraction or float)
|
|
28
|
+
Domain = TypeVar("Domain", int, Fraction, float) # Comparable Ring
|
|
29
|
+
Ratio = TypeVar("Ratio", Fraction, float)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class MinParametricAPI(Generic[Node, Edge, Ratio]):
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def distance(self, ratio: Ratio, edge: Edge) -> Ratio:
|
|
35
|
+
"""
|
|
36
|
+
The `distance` function calculates the distance between a given ratio and edge.
|
|
37
|
+
This is an abstract method that must be implemented by concrete subclasses.
|
|
38
|
+
|
|
39
|
+
:param ratio: The `ratio` parameter is of type `Ratio`. It represents a ratio or proportion
|
|
40
|
+
that affects the distance calculation.
|
|
41
|
+
:type ratio: Ratio
|
|
42
|
+
:param edge: The `edge` parameter represents an edge in a graph. It is of type `Edge`
|
|
43
|
+
:type edge: Edge
|
|
44
|
+
:return: The calculated distance based on the given ratio and edge
|
|
45
|
+
:rtype: Ratio
|
|
46
|
+
"""
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
def zero_cancel(self, cycle: Cycle) -> Ratio:
|
|
51
|
+
"""
|
|
52
|
+
The `zero_cancel` function takes a `Cycle` object as input and returns a `Ratio` object.
|
|
53
|
+
This calculates the ratio that would make the cycle's total distance sum to zero.
|
|
54
|
+
|
|
55
|
+
:param cycle: The `cycle` parameter is of type `Cycle`. It represents a cycle in the graph
|
|
56
|
+
that needs to be evaluated.
|
|
57
|
+
:type cycle: Cycle
|
|
58
|
+
:return: The ratio that would make the cycle's total distance zero
|
|
59
|
+
:rtype: Ratio
|
|
60
|
+
"""
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class MinParametricSolver(Generic[Node, Edge, Ratio]):
|
|
65
|
+
"""Minimum Parametric Solver
|
|
66
|
+
|
|
67
|
+
This class solves the following parametric network problem:
|
|
68
|
+
|
|
69
|
+
| min r
|
|
70
|
+
| s.t. dist[v] - dist[u] <= distrance(e, r)
|
|
71
|
+
| forall e(u, v) in G(V, E)
|
|
72
|
+
|
|
73
|
+
A parametric network problem refers to a type of optimization problem that
|
|
74
|
+
involves finding the optimal solution to a network flow problem as a function
|
|
75
|
+
of one single parameter.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def __init__(
|
|
79
|
+
self,
|
|
80
|
+
digraph: Mapping[Node, Mapping[Node, Edge]],
|
|
81
|
+
omega: MinParametricAPI[Node, Edge, Ratio],
|
|
82
|
+
) -> None:
|
|
83
|
+
"""
|
|
84
|
+
The `__init__` function initializes the solver with a graph and parametric API.
|
|
85
|
+
|
|
86
|
+
:param digraph: A mapping representing a directed graph where each node maps to its
|
|
87
|
+
neighbors and the edges connecting them. This defines the network structure
|
|
88
|
+
that the solver will work with.
|
|
89
|
+
:type digraph: Mapping[Node, Mapping[Node, Edge]]
|
|
90
|
+
:param omega: An instance of MinParametricAPI that provides the necessary methods
|
|
91
|
+
for distance calculation and cycle analysis. This parameterizes the
|
|
92
|
+
solver's behavior.
|
|
93
|
+
:type omega: MinParametricAPI[Node, Edge, Ratio]
|
|
94
|
+
"""
|
|
95
|
+
# self.ncf = NegCycleFinder(digraph)
|
|
96
|
+
self.digraph = digraph
|
|
97
|
+
self.omega: MinParametricAPI[Node, Edge, Ratio] = omega
|
|
98
|
+
|
|
99
|
+
def run(
|
|
100
|
+
self,
|
|
101
|
+
dist: MutableMapping[Node, Domain],
|
|
102
|
+
ratio: Ratio,
|
|
103
|
+
update_ok: Callable[[Domain, Domain], bool],
|
|
104
|
+
pick_one_only=False,
|
|
105
|
+
) -> Tuple[Ratio, Cycle]:
|
|
106
|
+
"""
|
|
107
|
+
The `run` function executes the parametric solver algorithm to find the minimum ratio.
|
|
108
|
+
|
|
109
|
+
:param dist: A mutable mapping of node distances that will be updated during the algorithm.
|
|
110
|
+
Represents the current distance estimates between nodes.
|
|
111
|
+
:type dist: MutableMapping[Node, Domain]
|
|
112
|
+
:param ratio: The initial ratio value to start the optimization from.
|
|
113
|
+
:type ratio: Ratio
|
|
114
|
+
:param update_ok: A callback function that determines whether a distance update is acceptable.
|
|
115
|
+
Takes current and new distance values, returns True if update should proceed.
|
|
116
|
+
:type update_ok: Callable[[Domain, Domain], bool]
|
|
117
|
+
:param pick_one_only: If True, stops after finding the first improving cycle. Defaults to False.
|
|
118
|
+
:type pick_one_only: bool
|
|
119
|
+
:return: A tuple containing:
|
|
120
|
+
- The minimum ratio found (ratio)
|
|
121
|
+
- The cycle that corresponds to this ratio (cycle)
|
|
122
|
+
"""
|
|
123
|
+
# Determine the numeric type used in distance calculations
|
|
124
|
+
D = type(next(iter(dist.values())))
|
|
125
|
+
|
|
126
|
+
# Helper function to calculate edge weights based on current ratio
|
|
127
|
+
def get_weight(e: Edge) -> Domain:
|
|
128
|
+
return D(self.omega.distance(ratio, e))
|
|
129
|
+
|
|
130
|
+
# Initialize tracking variables for minimum ratio and corresponding cycle
|
|
131
|
+
r_max = ratio
|
|
132
|
+
c_max = []
|
|
133
|
+
cycle = []
|
|
134
|
+
reverse: bool = True # Flag to alternate search direction
|
|
135
|
+
|
|
136
|
+
# Initialize the negative cycle finder with our graph
|
|
137
|
+
ncf: NegCycleFinder[Node, Edge, Domain] = NegCycleFinder(self.digraph)
|
|
138
|
+
|
|
139
|
+
# Main optimization loop
|
|
140
|
+
while True:
|
|
141
|
+
# Search for cycles in either forward or reverse direction
|
|
142
|
+
if reverse:
|
|
143
|
+
cycles = ncf.howard_succ(dist, get_weight, update_ok)
|
|
144
|
+
else:
|
|
145
|
+
cycles = ncf.howard_pred(dist, get_weight, update_ok)
|
|
146
|
+
|
|
147
|
+
# Evaluate all found cycles
|
|
148
|
+
for c_i in cycles:
|
|
149
|
+
r_i = self.omega.zero_cancel(c_i)
|
|
150
|
+
if r_max < r_i:
|
|
151
|
+
r_max = r_i
|
|
152
|
+
c_max = c_i
|
|
153
|
+
if pick_one_only: # Early exit if we only need one improvement
|
|
154
|
+
break
|
|
155
|
+
|
|
156
|
+
# Termination condition: no better ratio found
|
|
157
|
+
if r_max <= ratio:
|
|
158
|
+
break
|
|
159
|
+
|
|
160
|
+
# Update state for next iteration
|
|
161
|
+
cycle = c_max
|
|
162
|
+
ratio = r_max
|
|
163
|
+
reverse = not reverse # Alternate search direction
|
|
164
|
+
|
|
165
|
+
return ratio, cycle
|