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.
Files changed (66) hide show
  1. digraphx-0.2/GEMINI.md +65 -0
  2. {digraphx-0.1/src/digraphx.egg-info → digraphx-0.2}/PKG-INFO +1 -1
  3. {digraphx-0.1 → digraphx-0.2}/src/digraphx/min_cycle_ratio.py +35 -38
  4. {digraphx-0.1 → digraphx-0.2}/src/digraphx/min_parmetric_q.py +165 -150
  5. {digraphx-0.1 → digraphx-0.2}/src/digraphx/neg_cycle.py +79 -78
  6. {digraphx-0.1 → digraphx-0.2}/src/digraphx/neg_cycle_q.py +107 -123
  7. {digraphx-0.1 → digraphx-0.2}/src/digraphx/parametric.py +30 -6
  8. {digraphx-0.1 → digraphx-0.2}/src/digraphx/tiny_digraph.py +54 -18
  9. {digraphx-0.1 → digraphx-0.2/src/digraphx.egg-info}/PKG-INFO +1 -1
  10. {digraphx-0.1 → digraphx-0.2}/src/digraphx.egg-info/SOURCES.txt +3 -1
  11. {digraphx-0.1 → digraphx-0.2}/tests/test_cycle_ratio.py +24 -0
  12. digraphx-0.2/tests/test_min_parmetric_q.py +30 -0
  13. {digraphx-0.1 → digraphx-0.2}/tests/test_neg_cycle.py +37 -1
  14. {digraphx-0.1 → digraphx-0.2}/tests/test_neg_cycle_q.py +58 -1
  15. digraphx-0.2/tests/test_tiny_digraph.py +48 -0
  16. digraphx-0.1/src/digraphx/max_cycle_ratio.tpy +0 -134
  17. {digraphx-0.1 → digraphx-0.2}/.coveragerc +0 -0
  18. {digraphx-0.1 → digraphx-0.2}/.github/workflows/ci.bak +0 -0
  19. {digraphx-0.1 → digraphx-0.2}/.github/workflows/jekyll-gh-pages.yml +0 -0
  20. {digraphx-0.1 → digraphx-0.2}/.github/workflows/multi-platforms.yml +0 -0
  21. {digraphx-0.1 → digraphx-0.2}/.github/workflows/python-app.yml +0 -0
  22. {digraphx-0.1 → digraphx-0.2}/.github/workflows/python-publish.yml +0 -0
  23. {digraphx-0.1 → digraphx-0.2}/.gitignore +0 -0
  24. {digraphx-0.1 → digraphx-0.2}/.isort.cfg +0 -0
  25. {digraphx-0.1 → digraphx-0.2}/.pre-commit-config.yaml +0 -0
  26. {digraphx-0.1 → digraphx-0.2}/.readthedocs.yml +0 -0
  27. {digraphx-0.1 → digraphx-0.2}/AUTHORS.md +0 -0
  28. {digraphx-0.1 → digraphx-0.2}/CHANGELOG.md +0 -0
  29. {digraphx-0.1 → digraphx-0.2}/CONTRIBUTING.md +0 -0
  30. {digraphx-0.1 → digraphx-0.2}/LICENSE +0 -0
  31. {digraphx-0.1 → digraphx-0.2}/LICENSE.txt +0 -0
  32. {digraphx-0.1 → digraphx-0.2}/README.md +0 -0
  33. {digraphx-0.1 → digraphx-0.2}/docs/Makefile +0 -0
  34. {digraphx-0.1 → digraphx-0.2}/docs/_static/.gitignore +0 -0
  35. {digraphx-0.1 → digraphx-0.2}/docs/authors.md +0 -0
  36. {digraphx-0.1 → digraphx-0.2}/docs/changelog.md +0 -0
  37. {digraphx-0.1 → digraphx-0.2}/docs/conf.py +0 -0
  38. {digraphx-0.1 → digraphx-0.2}/docs/contributing.md +0 -0
  39. {digraphx-0.1 → digraphx-0.2}/docs/index.md +0 -0
  40. {digraphx-0.1 → digraphx-0.2}/docs/license.md +0 -0
  41. {digraphx-0.1 → digraphx-0.2}/docs/readme.md +0 -0
  42. {digraphx-0.1 → digraphx-0.2}/docs/requirements.txt +0 -0
  43. {digraphx-0.1 → digraphx-0.2}/environment.yml +0 -0
  44. {digraphx-0.1 → digraphx-0.2}/experiments/plot_node_colormap.ipynb +0 -0
  45. {digraphx-0.1 → digraphx-0.2}/experiments/plot_node_colormap.py +0 -0
  46. {digraphx-0.1 → digraphx-0.2}/mypy.ini +0 -0
  47. {digraphx-0.1 → digraphx-0.2}/note.md +0 -0
  48. {digraphx-0.1 → digraphx-0.2}/pyproject.toml +0 -0
  49. {digraphx-0.1 → digraphx-0.2}/requirements/README.md +0 -0
  50. {digraphx-0.1 → digraphx-0.2}/requirements/default.txt +0 -0
  51. {digraphx-0.1 → digraphx-0.2}/requirements/doc.txt +0 -0
  52. {digraphx-0.1 → digraphx-0.2}/requirements/test.txt +0 -0
  53. {digraphx-0.1 → digraphx-0.2}/requirements.txt +0 -0
  54. {digraphx-0.1 → digraphx-0.2}/setup.cfg +0 -0
  55. {digraphx-0.1 → digraphx-0.2}/setup.py +0 -0
  56. {digraphx-0.1 → digraphx-0.2}/src/digraphx/__init__.py +0 -0
  57. {digraphx-0.1 → digraphx-0.2}/src/digraphx/skeleton.py +0 -0
  58. {digraphx-0.1 → digraphx-0.2}/src/digraphx.egg-info/dependency_links.txt +0 -0
  59. {digraphx-0.1 → digraphx-0.2}/src/digraphx.egg-info/not-zip-safe +0 -0
  60. {digraphx-0.1 → digraphx-0.2}/src/digraphx.egg-info/requires.txt +0 -0
  61. {digraphx-0.1 → digraphx-0.2}/src/digraphx.egg-info/top_level.txt +0 -0
  62. {digraphx-0.1 → digraphx-0.2}/tests/__init__.py +0 -0
  63. {digraphx-0.1 → digraphx-0.2}/tests/conftest.py +0 -0
  64. {digraphx-0.1 → digraphx-0.2}/tests/test_skeleton.py +0 -0
  65. {digraphx-0.1 → digraphx-0.2}/tests/travis_install.sh +0 -0
  66. {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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: digraphx
3
- Version: 0.1
3
+ Version: 0.2
4
4
  Summary: Network Optimization Python Code
5
5
  Home-page: https://github.com/luk036/digraphx
6
6
  Author: Wai-Shing Luk
@@ -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
- This function initializes an object with two parameters, `digraph` and `result_type`, and assigns them to instance
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 parameter `result_type` is a type. It is used to specify the type of the variable `result_type`. The type
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
- The function calculates the distance based on the ratio and edge information.
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 `edge` parameter is a mutable mapping (dictionary-like object) that contains
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: the result of the expression `self.result_type(edge["cost"]) - ratio * edge["time"]`.
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
- The `zero_cancel` function calculates the ratio of the cost to time for a given cycle.
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: a Ratio object.
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
- The function initializes an instance of a class with a graph object.
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
- This function takes a distance mapping and a ratio as input, and returns a ratio and a cycle.
152
+ Run the minimum cycle ratio solver algorithm.
159
153
 
160
- :param dist: A mutable mapping that maps each node in the graph to a ratio value. This represents
161
- the initial distribution of ratios for each node
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: The parameter `r0` is of type `Ratio` and represents the initial ratio value
166
-
162
+ :param r0: Initial ratio value to start the search
167
163
  :type r0: Ratio
168
164
 
169
- :return: The function `run` returns a tuple containing the ratio and cycle.
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 this 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 Generic, Mapping, MutableMapping, Tuple, TypeVar, Callable
24
-
25
- from .neg_cycle_q import Cycle, Edge, NegCycleFinder, Node
26
-
27
- Domain = TypeVar("Domain", int, Fraction, float) # Comparable Ring
28
- Ratio = TypeVar("Ratio", Fraction, float)
29
-
30
-
31
- class MinParametricAPI(Generic[Node, Edge, Ratio]):
32
- @abstractmethod
33
- def distance(self, ratio: Ratio, edge: Edge) -> Ratio:
34
- """
35
- The `distance` function calculates the distance between a given ratio and edge.
36
-
37
- :param ratio: The `ratio` parameter is of type `Ratio`. It represents a ratio or proportion
38
- :type ratio: Ratio
39
- :param edge: The `edge` parameter represents an edge in a graph. It is of type `Edge`
40
- :type edge: Edge
41
- """
42
- pass
43
-
44
- @abstractmethod
45
- def zero_cancel(self, cycle: Cycle) -> Ratio:
46
- """
47
- The `zero_cancel` function takes a `Cycle` object as input and returns a `Ratio` object.
48
-
49
- :param cycle: The `cycle` parameter is of type `Cycle`.
50
- :type cycle: Cycle
51
- """
52
- pass
53
-
54
-
55
- class MinParametricSolver(Generic[Node, Edge, Ratio]):
56
- """Minimum Parametric Solver
57
-
58
- This class solves the following parametric network problem:
59
-
60
- | min r
61
- | s.t. dist[v] - dist[u] <= distrance(e, r)
62
- | forall e(u, v) in G(V, E)
63
-
64
- A parametric network problem refers to a type of optimization problem that
65
- involves finding the optimal solution to a network flow problem as a function
66
- of one single parameter.
67
- """
68
-
69
- def __init__(
70
- self,
71
- digraph: Mapping[Node, Mapping[Node, Edge]],
72
- omega: MinParametricAPI[Node, Edge, Ratio],
73
- ) -> None:
74
- """
75
- The `__init__` function initializes an object with a graph and an omega parameter.
76
-
77
- :param digraph: digraph is a mapping of nodes to a mapping of nodes to edges. It represents a graph
78
- where each node is connected to other nodes through edges. The edges are represented by the
79
- mapping of nodes to edges
80
-
81
- :type digraph: Mapping[Node, Mapping[Node, Edge]]
82
-
83
- :param omega: The `omega` parameter is an instance of the `ParametricAPI` class. It represents
84
- some kind of parametric API that takes three type parameters: `Node`, `Edge`, and `Ratio`
85
-
86
- :type omega: ParametricAPI[Node, Edge, Ratio]
87
- """
88
- # self.ncf = NegCycleFinder(digraph)
89
- self.digraph = digraph
90
- self.omega: MinParametricAPI[Node, Edge, Ratio] = omega
91
-
92
- def run(
93
- self,
94
- dist: MutableMapping[Node, Domain],
95
- ratio: Ratio,
96
- update_ok: Callable[[Domain, Domain], bool],
97
- pick_one_only=False,
98
- ) -> Tuple[Ratio, Cycle]:
99
- """
100
- The `run` function takes in a distance mapping and a ratio, and iteratively finds the minimum
101
- ratio and corresponding cycle until the minimum ratio is greater than or equal to the input
102
- ratio.
103
-
104
- :param dist: The `dist` parameter is a mutable mapping where the keys are `Node` objects and the
105
- values are `Domain` objects. It represents the distance between nodes in a graph
106
-
107
- :type dist: MutableMapping[Node, Domain]
108
-
109
- :param ratio: The `ratio` parameter is a value that represents a ratio or proportion. It is used
110
- as a threshold or target value in the algorithm
111
-
112
- :type ratio: Ratio
113
-
114
- :param update_ok: The `update_ok` parameter is a function that determines whether an update to the
115
- distance `dist[vtx_v]` is allowed. It takes two arguments: the current value of `dist[vtx_v]` and
116
- the new value `d`. It should return `True` if the update is
117
-
118
- :return: The function `run` returns a tuple containing the updated ratio (`ratio`) and the cycle (`cycle`).
119
- """
120
- D = type(next(iter(dist.values())))
121
-
122
- def get_weight(e: Edge) -> Domain:
123
- return D(self.omega.distance(ratio, e))
124
-
125
- r_max = ratio
126
- c_max = []
127
- cycle = []
128
- reverse: bool = True
129
-
130
- ncf: NegCycleFinder[Node, Edge, Domain] = NegCycleFinder(self.digraph)
131
-
132
- while True:
133
- if reverse:
134
- cycles = ncf.howard_succ(dist, get_weight, update_ok)
135
- else:
136
- cycles = ncf.howard_pred(dist, get_weight, update_ok)
137
- for c_i in cycles:
138
- r_i = self.omega.zero_cancel(c_i)
139
- if r_max < r_i:
140
- r_max = r_i
141
- c_max = c_i
142
- if pick_one_only:
143
- break
144
- if r_max <= ratio:
145
- break
146
-
147
- cycle = c_max
148
- ratio = r_max
149
- reverse = not reverse
150
- return ratio, cycle
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