digraphx 0.1__py3-none-any.whl
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/__init__.py +16 -0
- digraphx/max_cycle_ratio.tpy +134 -0
- digraphx/min_cycle_ratio.py +174 -0
- digraphx/min_parmetric_q.py +150 -0
- digraphx/neg_cycle.py +241 -0
- digraphx/neg_cycle_q.py +342 -0
- digraphx/parametric.py +135 -0
- digraphx/skeleton.py +149 -0
- digraphx/tiny_digraph.py +79 -0
- digraphx-0.1.dist-info/METADATA +56 -0
- digraphx-0.1.dist-info/RECORD +14 -0
- digraphx-0.1.dist-info/WHEEL +5 -0
- digraphx-0.1.dist-info/licenses/LICENSE.txt +21 -0
- digraphx-0.1.dist-info/top_level.txt +1 -0
digraphx/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
if sys.version_info[:2] >= (3, 8):
|
|
4
|
+
# TODO: Import directly (no need for conditional) when `python_requires = >= 3.9`
|
|
5
|
+
from importlib.metadata import PackageNotFoundError, version # pragma: no cover
|
|
6
|
+
else:
|
|
7
|
+
from importlib_metadata import PackageNotFoundError, version # pragma: no cover
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
# Change here if project is renamed and does not equal the package name
|
|
11
|
+
dist_name = __name__
|
|
12
|
+
__version__ = version(dist_name)
|
|
13
|
+
except PackageNotFoundError: # pragma: no cover
|
|
14
|
+
__version__ = "unknown"
|
|
15
|
+
finally:
|
|
16
|
+
del version, PackageNotFoundError
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from fractions import Fraction
|
|
2
|
+
from typing import Generic, Mapping, MutableMapping, Tuple, TypeVar
|
|
3
|
+
|
|
4
|
+
from .neg_cycle_q import Cycle, Domain, Edge, Node
|
|
5
|
+
from .min_parametric_q import MinParametricSolver, MinParametricAPI
|
|
6
|
+
|
|
7
|
+
Ratio = TypeVar("Ratio", Fraction, float) # Comparable field
|
|
8
|
+
Graph = Mapping[Node, Mapping[Node, Mapping[str, Domain]]]
|
|
9
|
+
GraphMut = MutableMapping[Node, MutableMapping[Node, MutableMapping[str, Domain]]]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def set_default(digraph: GraphMut, weight: str, value: Domain) -> None:
|
|
13
|
+
"""
|
|
14
|
+
This function sets a default value for a specified weight in a graph.
|
|
15
|
+
|
|
16
|
+
:param digraph: The parameter `digraph` is of type `GraphMut`, which is likely a mutable graph data
|
|
17
|
+
structure. It represents a graph where each node has a dictionary of neighbors and their
|
|
18
|
+
corresponding edge attributes
|
|
19
|
+
:type digraph: GraphMut
|
|
20
|
+
:param weight: The `weight` parameter is a string that represents the weight attribute of the edges
|
|
21
|
+
in the graph
|
|
22
|
+
:type weight: str
|
|
23
|
+
:param value: The `value` parameter is the default value that will be set for the specified weight
|
|
24
|
+
attribute in the graph
|
|
25
|
+
:type value: Domain
|
|
26
|
+
"""
|
|
27
|
+
for _, neighbors in digraph.items():
|
|
28
|
+
for _, e in neighbors.items():
|
|
29
|
+
if e.get(weight, None) is None:
|
|
30
|
+
e[weight] = value
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# The `MaxCycleRatioAPI` class is a parametric API that calculates the ratio of a cycle based on the cost
|
|
34
|
+
# and time of its edges.
|
|
35
|
+
class MaxCycleRatioAPI(MinParametricAPI[Node, MutableMapping[str, Domain], Ratio]):
|
|
36
|
+
def __init__(
|
|
37
|
+
self, digraph: Mapping[Node, Mapping[Node, Mapping[str, Domain]]], result_type: type
|
|
38
|
+
) -> None:
|
|
39
|
+
"""
|
|
40
|
+
This function initializes an object with two parameters, `digraph` and `result_type`, and assigns them to instance
|
|
41
|
+
variables.
|
|
42
|
+
|
|
43
|
+
:param digraph: A mapping of nodes to a mapping of nodes to a mapping of strings to domains. It
|
|
44
|
+
represents a graph structure where each node is connected to other nodes through edges, and each
|
|
45
|
+
edge has associated attributes represented by strings and domains
|
|
46
|
+
:type digraph: Mapping[Node, Mapping[Node, Mapping[str, Domain]]]
|
|
47
|
+
:param result_type: The parameter `result_type` is a type. It is used to specify the type of the variable `result_type`. The type
|
|
48
|
+
can be any valid Python type, such as `int`, `str`, `list`, etc
|
|
49
|
+
:type result_type: type
|
|
50
|
+
"""
|
|
51
|
+
self.digraph: Mapping[Node, Mapping[Node, Mapping[str, Domain]]] = digraph
|
|
52
|
+
self.result_type = result_type
|
|
53
|
+
|
|
54
|
+
def distance(self, ratio: Ratio, edge: MutableMapping[str, Domain]) -> Ratio:
|
|
55
|
+
"""
|
|
56
|
+
The function calculates the distance based on the ratio and edge information.
|
|
57
|
+
|
|
58
|
+
:param ratio: The ratio parameter is of type Ratio. It is used in the calculation of the return
|
|
59
|
+
value
|
|
60
|
+
:type ratio: Ratio
|
|
61
|
+
:param edge: The `edge` parameter is a mutable mapping (dictionary-like object) that contains
|
|
62
|
+
information about a specific edge in a graph. It has two keys: "cost" and "time". The value
|
|
63
|
+
associated with the "cost" key represents the cost of traversing the edge, while the value
|
|
64
|
+
associated with
|
|
65
|
+
:type edge: MutableMapping[str, Domain]
|
|
66
|
+
:return: the result of the expression `self.result_type(edge["cost"]) - ratio * edge["time"]`.
|
|
67
|
+
"""
|
|
68
|
+
return self.result_type(edge["cost"]) - ratio * edge["time"]
|
|
69
|
+
|
|
70
|
+
def zero_cancel(self, cycle: Cycle) -> Ratio:
|
|
71
|
+
"""
|
|
72
|
+
The `zero_cancel` function calculates the ratio of the cost to time for a given cycle.
|
|
73
|
+
|
|
74
|
+
:param cycle: The `cycle` parameter is of type `Cycle`. It represents a cycle, which is a sequence
|
|
75
|
+
of edges in a graph that starts and ends at the same vertex. Each edge in the cycle is a dictionary
|
|
76
|
+
with keys "cost" and "time", representing the cost and time associated with that edge
|
|
77
|
+
:type cycle: Cycle
|
|
78
|
+
:return: a Ratio object.
|
|
79
|
+
"""
|
|
80
|
+
total_cost = sum(edge["cost"] for edge in cycle)
|
|
81
|
+
total_time = sum(edge["time"] for edge in cycle)
|
|
82
|
+
return self.result_type(total_cost) / total_time
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# The `MaxCycleRatioSolver` class is a solver for the minimum cycle ratio problem in directed graphs.
|
|
86
|
+
class MaxCycleRatioSolver(Generic[Node, Edge, Ratio]):
|
|
87
|
+
"""Minimum Cycle Ratio Solver
|
|
88
|
+
|
|
89
|
+
This class solves the following parametric network problem:
|
|
90
|
+
|
|
91
|
+
min r
|
|
92
|
+
s.t. dist[v] - dist[u] <= cost(u, v) - ratio * time(u, v)
|
|
93
|
+
for all (u, v) in E
|
|
94
|
+
|
|
95
|
+
The maximum cycle ratio (MCR) problem is a fundamental problem in the
|
|
96
|
+
analysis of directed graphs. Given a directed graph, the MCR problem seeks to
|
|
97
|
+
find the cycle with the minimum ratio of the sum of edge weights to the
|
|
98
|
+
number of edges in the cycle. In other words, the MCR problem seeks to find
|
|
99
|
+
the "tightest" cycle in the graph, where the tightness of a cycle is measured
|
|
100
|
+
by the ratio of the total weight of the cycle to its length.
|
|
101
|
+
|
|
102
|
+
The MCR problem has many applications in the analysis of discrete event
|
|
103
|
+
systems, such as digital circuits and communication networks. It is closely
|
|
104
|
+
related to other problems in graph theory, such as the shortest path problem
|
|
105
|
+
and the maximum flow problem. Efficient algorithms for solving the MCR
|
|
106
|
+
problem are therefore of great practical importance.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
def __init__(self, digraph: Graph) -> None:
|
|
110
|
+
"""
|
|
111
|
+
The function initializes an instance of a class with a graph object.
|
|
112
|
+
|
|
113
|
+
:param digraph: The `digraph` parameter is a mapping of nodes to a mapping of nodes to any type of value. It
|
|
114
|
+
represents a graph where each node is associated with a set of neighboring nodes and their
|
|
115
|
+
corresponding values
|
|
116
|
+
:type digraph: Graph
|
|
117
|
+
"""
|
|
118
|
+
self.digraph: Graph = digraph
|
|
119
|
+
|
|
120
|
+
def run(self, dist: MutableMapping[Node, Domain], r0: Ratio) -> Tuple[Ratio, Cycle]:
|
|
121
|
+
"""
|
|
122
|
+
This function takes a distance mapping and a ratio as input, and returns a ratio and a cycle.
|
|
123
|
+
|
|
124
|
+
:param dist: A mutable mapping that maps each node in the graph to a ratio value. This represents
|
|
125
|
+
the initial distribution of ratios for each node
|
|
126
|
+
:type dist: MutableMapping[Node, Domain]
|
|
127
|
+
:param r0: The parameter `r0` is of type `Ratio` and represents the initial ratio value
|
|
128
|
+
:type r0: Ratio
|
|
129
|
+
:return: The function `run` returns a tuple containing the ratio and cycle.
|
|
130
|
+
"""
|
|
131
|
+
omega = MaxCycleRatioAPI(self.digraph, type(r0))
|
|
132
|
+
solver = MinParametricSolver(self.digraph, omega)
|
|
133
|
+
ratio, cycle = solver.run(dist, r0)
|
|
134
|
+
return ratio, cycle
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Minimum Cycle Ratio Solver
|
|
3
|
+
|
|
4
|
+
This code implements a Minimum Cycle Ratio (MCR) Solver for directed graphs. The purpose of this code is to find the cycle in a graph that has the smallest ratio of total edge weights to the number of edges in the cycle. This is useful in analyzing various systems like digital circuits and communication networks.
|
|
5
|
+
|
|
6
|
+
The main input for this solver is a directed graph, represented as a mapping of nodes to their neighboring nodes and associated edge attributes. The graph is expected to have "cost" and "time" attributes for each edge.
|
|
7
|
+
|
|
8
|
+
The primary output of this solver is a tuple containing two elements: the minimum cycle ratio (a number) and the cycle itself (a sequence of edges that form the cycle with the minimum ratio).
|
|
9
|
+
|
|
10
|
+
To achieve its purpose, the code uses a parametric approach. It defines a CycleRatioAPI class that calculates distances between nodes based on a given ratio and edge information. This class also computes the ratio for a given cycle.
|
|
11
|
+
|
|
12
|
+
The main solver, MinCycleRatioSolver, uses the CycleRatioAPI in combination with a MaxParametricSolver (which is not fully shown in this code snippet) to iteratively find the minimum cycle ratio. It starts with an initial ratio and distance mapping for each node, and then refines these values until it finds the optimal solution.
|
|
13
|
+
|
|
14
|
+
The algorithm works by repeatedly adjusting the ratio and recalculating distances between nodes. It looks for cycles where the sum of distances around the cycle is negative, which indicates a cycle with a lower ratio than the current best. This process continues until no such cycle can be found, at which point the minimum cycle ratio has been determined.
|
|
15
|
+
|
|
16
|
+
An important aspect of the code is how it handles different types of numbers. It uses generic types and can work with both fractions and floating-point numbers, allowing for flexibility in how precise the calculations need to be.
|
|
17
|
+
|
|
18
|
+
The code also includes utility functions like set_default, which ensures that all edges in the graph have a specified weight attribute, setting a default value if it's missing. This helps in preparing the graph data for the main algorithm.
|
|
19
|
+
|
|
20
|
+
Overall, this code provides a flexible and powerful tool for analyzing directed graphs, particularly useful in scenarios where understanding the most "efficient" or "tightest" cycles in a system is important.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from fractions import Fraction
|
|
24
|
+
from typing import Generic, Mapping, MutableMapping, Tuple, TypeVar
|
|
25
|
+
|
|
26
|
+
from .neg_cycle import Cycle, Domain, Edge, Node
|
|
27
|
+
from .parametric import MaxParametricSolver, ParametricAPI
|
|
28
|
+
|
|
29
|
+
Ratio = TypeVar("Ratio", Fraction, float)
|
|
30
|
+
Graph = Mapping[Node, Mapping[Node, Mapping[str, Domain]]]
|
|
31
|
+
GraphMut = MutableMapping[Node, MutableMapping[Node, MutableMapping[str, Domain]]]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def set_default(digraph: GraphMut, weight: str, value: Domain) -> None:
|
|
35
|
+
"""
|
|
36
|
+
This function sets a default value for a specified weight in a graph.
|
|
37
|
+
|
|
38
|
+
:param digraph: The parameter `digraph` is of type `GraphMut`, which is likely a mutable graph data
|
|
39
|
+
structure. It represents a graph where each node has a dictionary of neighbors and their
|
|
40
|
+
corresponding edge attributes
|
|
41
|
+
|
|
42
|
+
:type digraph: GraphMut
|
|
43
|
+
|
|
44
|
+
:param weight: The `weight` parameter is a string that represents the weight attribute of the edges in the graph
|
|
45
|
+
|
|
46
|
+
:type weight: str
|
|
47
|
+
|
|
48
|
+
:param value: The `value` parameter is the default value that will be set for the specified weight attribute in the graph
|
|
49
|
+
|
|
50
|
+
:type value: Domain
|
|
51
|
+
"""
|
|
52
|
+
for _, neighbors in digraph.items():
|
|
53
|
+
for _, e in neighbors.items():
|
|
54
|
+
if e.get(weight, None) is None:
|
|
55
|
+
e[weight] = value
|
|
56
|
+
|
|
57
|
+
|
|
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
|
+
class CycleRatioAPI(ParametricAPI[Node, MutableMapping[str, Domain], Ratio]):
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
digraph: Mapping[Node, Mapping[Node, Mapping[str, Domain]]],
|
|
64
|
+
result_type: type,
|
|
65
|
+
) -> None:
|
|
66
|
+
"""
|
|
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
|
|
73
|
+
|
|
74
|
+
:type digraph: Mapping[Node, Mapping[Node, Mapping[str, Domain]]]
|
|
75
|
+
|
|
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
|
+
|
|
79
|
+
:type result_type: type
|
|
80
|
+
"""
|
|
81
|
+
self.digraph: Mapping[Node, Mapping[Node, Mapping[str, Domain]]] = digraph
|
|
82
|
+
self.result_type = result_type
|
|
83
|
+
|
|
84
|
+
def distance(self, ratio: Ratio, edge: MutableMapping[str, Domain]) -> Ratio:
|
|
85
|
+
"""
|
|
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
|
|
89
|
+
|
|
90
|
+
:type ratio: Ratio
|
|
91
|
+
|
|
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
|
+
|
|
97
|
+
:type edge: MutableMapping[str, Domain]
|
|
98
|
+
|
|
99
|
+
:return: the result of the expression `self.result_type(edge["cost"]) - ratio * edge["time"]`.
|
|
100
|
+
"""
|
|
101
|
+
return self.result_type(edge["cost"]) - ratio * edge["time"]
|
|
102
|
+
|
|
103
|
+
def zero_cancel(self, cycle: Cycle) -> Ratio:
|
|
104
|
+
"""
|
|
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
|
|
110
|
+
|
|
111
|
+
:type cycle: Cycle
|
|
112
|
+
|
|
113
|
+
:return: a Ratio object.
|
|
114
|
+
"""
|
|
115
|
+
total_cost = sum(edge["cost"] for edge in cycle)
|
|
116
|
+
total_time = sum(edge["time"] for edge in cycle)
|
|
117
|
+
return self.result_type(total_cost) / total_time
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# The `MinCycleRatioSolver` class is a solver for the minimum cycle ratio problem in directed graphs.
|
|
121
|
+
class MinCycleRatioSolver(Generic[Node, Edge, Ratio]):
|
|
122
|
+
"""Minimum Cycle Ratio Solver
|
|
123
|
+
|
|
124
|
+
This class solves the following parametric network problem:
|
|
125
|
+
|
|
126
|
+
| max r
|
|
127
|
+
| s.t. dist[v] - dist[u] <= cost(u, v) - ratio * time(u, v)
|
|
128
|
+
| for all (u, v) in E
|
|
129
|
+
|
|
130
|
+
The minimum cycle ratio (MCR) problem is a fundamental problem in the
|
|
131
|
+
analysis of directed graphs. Given a directed graph, the MCR problem seeks to
|
|
132
|
+
find the cycle with the minimum ratio of the sum of edge weights to the
|
|
133
|
+
number of edges in the cycle. In other words, the MCR problem seeks to find
|
|
134
|
+
the "tightest" cycle in the graph, where the tightness of a cycle is measured
|
|
135
|
+
by the ratio of the total weight of the cycle to its length.
|
|
136
|
+
|
|
137
|
+
The MCR problem has many applications in the analysis of discrete event
|
|
138
|
+
systems, such as digital circuits and communication networks. It is closely
|
|
139
|
+
related to other problems in graph theory, such as the shortest path problem
|
|
140
|
+
and the maximum flow problem. Efficient algorithms for solving the MCR
|
|
141
|
+
problem are therefore of great practical importance.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
def __init__(self, digraph: Graph) -> None:
|
|
145
|
+
"""
|
|
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
|
|
151
|
+
|
|
152
|
+
:type digraph: Graph
|
|
153
|
+
"""
|
|
154
|
+
self.digraph: Graph = digraph
|
|
155
|
+
|
|
156
|
+
def run(self, dist: MutableMapping[Node, Domain], r0: Ratio) -> Tuple[Ratio, Cycle]:
|
|
157
|
+
"""
|
|
158
|
+
This function takes a distance mapping and a ratio as input, and returns a ratio and a cycle.
|
|
159
|
+
|
|
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
|
|
162
|
+
|
|
163
|
+
:type dist: MutableMapping[Node, Domain]
|
|
164
|
+
|
|
165
|
+
:param r0: The parameter `r0` is of type `Ratio` and represents the initial ratio value
|
|
166
|
+
|
|
167
|
+
:type r0: Ratio
|
|
168
|
+
|
|
169
|
+
:return: The function `run` returns a tuple containing the ratio and cycle.
|
|
170
|
+
"""
|
|
171
|
+
omega = CycleRatioAPI(self.digraph, type(r0))
|
|
172
|
+
solver = MaxParametricSolver(self.digraph, omega)
|
|
173
|
+
ratio, cycle = solver.run(dist, r0)
|
|
174
|
+
return ratio, cycle
|
|
@@ -0,0 +1,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 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
|