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 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