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/parametric.py ADDED
@@ -0,0 +1,135 @@
1
+ """
2
+ Parametric Network Solver
3
+
4
+ This code defines a system for solving parametric network problems, which are a type of optimization problem in graph theory. The main purpose of this code is to find the maximum ratio that satisfies certain conditions in a graph, where the distances between nodes depend on this ratio.
5
+
6
+ The code takes two main inputs: a graph (represented as a mapping of nodes and edges) and an object that defines how to calculate distances based on the ratio. It produces two outputs: the maximum ratio that satisfies the conditions and a cycle in the graph that corresponds to this ratio.
7
+
8
+ The code achieves its purpose through an iterative algorithm implemented in the run method of the MaxParametricSolver class. This method starts with an initial ratio and repeatedly finds cycles in the graph that could potentially improve this ratio. It uses a negative cycle finder (NCF) to detect these cycles efficiently.
9
+
10
+ The algorithm works as follows:
11
+
12
+ 1. It starts with an initial ratio and distance estimates for each node.
13
+ 2. It uses the NCF to find cycles in the graph where the total distance is negative.
14
+ 3. For each negative cycle found, it calculates a new ratio that would make the cycle's total distance zero.
15
+ 4. If this new ratio is smaller than the current best ratio, it updates the best ratio and remembers this cycle.
16
+ 5. It repeats steps 2-4 until no better ratio can be found.
17
+
18
+ The main data transformation happening here is the continuous updating of the ratio based on the cycles found in the graph. The algorithm is essentially searching for the highest ratio that doesn't allow any negative cycles in the graph, when distances are calculated using this ratio.
19
+
20
+ This code is designed to be flexible, using generic types for nodes, edges, and ratios. This allows it to work with different types of graphs and different ways of calculating distances. The ParametricAPI class defines an interface for how distances should be calculated and how to find the ratio that makes a cycle's total distance zero.
21
+
22
+ Overall, this code provides a framework for solving a specific type of optimization problem on graphs, where the goal is to maximize a ratio while maintaining certain constraints on the distances between nodes in the graph.
23
+ """
24
+
25
+ from abc import abstractmethod
26
+ from fractions import Fraction
27
+ from typing import Generic, Mapping, MutableMapping, Tuple, TypeVar
28
+
29
+ from .neg_cycle import Cycle, Domain, Edge, NegCycleFinder, Node
30
+
31
+ Ratio = TypeVar("Ratio", Fraction, float)
32
+
33
+
34
+ class ParametricAPI(Generic[Node, Edge, Ratio]):
35
+ @abstractmethod
36
+ def distance(self, ratio: Ratio, edge: Edge) -> Ratio:
37
+ """
38
+ The `distance` function calculates the distance between a given ratio and edge.
39
+
40
+ :param ratio: The `ratio` parameter is of type `Ratio`. It represents a ratio or proportion
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
+ """
45
+
46
+ @abstractmethod
47
+ def zero_cancel(self, cycle: Cycle) -> Ratio:
48
+ """
49
+ The `zero_cancel` function takes a `Cycle` object as input and returns a `Ratio` object.
50
+
51
+ :param cycle: The `cycle` parameter is of type `Cycle`.
52
+ :type cycle: Cycle
53
+ """
54
+
55
+
56
+ class MaxParametricSolver(Generic[Node, Edge, Ratio]):
57
+ """Maximum Parametric Solver
58
+
59
+ This class solves the following parametric network problem:
60
+
61
+ | max r
62
+ | s.t. dist[v] - dist[u] <= distrance(e, r)
63
+ | forall e(u, v) in G(V, E)
64
+
65
+ A parametric network problem refers to a type of optimization problem that
66
+ involves finding the optimal solution to a network flow problem as a function
67
+ of one single parameter.
68
+ """
69
+
70
+ def __init__(
71
+ self,
72
+ digraph: Mapping[Node, Mapping[Node, Edge]],
73
+ omega: ParametricAPI[Node, Edge, Ratio],
74
+ ) -> None:
75
+ """
76
+ The `__init__` function initializes an object with a graph and an omega parameter.
77
+
78
+ :param digraph: digraph is a mapping of nodes to a mapping of nodes to edges. It represents a graph
79
+ where each node is connected to other nodes through edges. The edges are represented by the
80
+ mapping of nodes to edges
81
+
82
+ :type digraph: Mapping[Node, Mapping[Node, Edge]]
83
+
84
+ :param omega: The `omega` parameter is an instance of the `ParametricAPI` class. It represents
85
+ some kind of parametric API that takes three type parameters: `Node`, `Edge`, and `Ratio`
86
+
87
+ :type omega: ParametricAPI[Node, Edge, Ratio]
88
+ """
89
+ # self.ncf = NegCycleFinder(digraph)
90
+ self.digraph = digraph
91
+ self.omega: ParametricAPI[Node, Edge, Ratio] = omega
92
+
93
+ def run(
94
+ self, dist: MutableMapping[Node, Domain], ratio: Ratio
95
+ ) -> Tuple[Ratio, Cycle]:
96
+ """
97
+ The `run` function takes in a distance mapping and a ratio, and iteratively finds the minimum
98
+ ratio and corresponding cycle until the minimum ratio is greater than or equal to the input
99
+ ratio.
100
+
101
+ :param dist: The `dist` parameter is a mutable mapping where the keys are `Node` objects and the
102
+ values are `Domain` objects. It represents the distance between nodes in a graph
103
+
104
+ :type dist: MutableMapping[Node, Domain]
105
+
106
+ :param ratio: The `ratio` parameter is a value that represents a ratio or proportion. It is used
107
+ as a threshold or target value in the algorithm
108
+
109
+ :type ratio: Ratio
110
+
111
+ :return: The function `run` returns a tuple containing the updated ratio (`ratio`) and the cycle (`cycle`).
112
+ """
113
+ D = type(next(iter(dist.values())))
114
+
115
+ def get_weight(e: Edge) -> Domain:
116
+ return D(self.omega.distance(ratio, e))
117
+
118
+ r_min = ratio
119
+ c_min = []
120
+ cycle = []
121
+
122
+ ncf: NegCycleFinder[Node, Edge, Domain] = NegCycleFinder(self.digraph)
123
+
124
+ while True:
125
+ for ci in ncf.howard(dist, get_weight):
126
+ ri = self.omega.zero_cancel(ci)
127
+ if r_min > ri:
128
+ r_min = ri
129
+ c_min = ci
130
+ if r_min >= ratio:
131
+ break
132
+
133
+ cycle = c_min
134
+ ratio = r_min
135
+ return ratio, cycle
digraphx/skeleton.py ADDED
@@ -0,0 +1,149 @@
1
+ """
2
+ This is a skeleton file that can serve as a starting point for a Python
3
+ console script. To run this script uncomment the following lines in the
4
+ ``[options.entry_points]`` section in ``setup.cfg``::
5
+
6
+ console_scripts =
7
+ fibonacci = digraphx.skeleton:run
8
+
9
+ Then run ``pip install .`` (or ``pip install -e .`` for editable mode)
10
+ which will install the command ``fibonacci`` inside your current environment.
11
+
12
+ Besides console scripts, the header (i.e. until ``_logger``...) of this file can
13
+ also be used as template for Python modules.
14
+
15
+ Note:
16
+ This file can be renamed depending on your needs or safely removed if not needed.
17
+
18
+ References:
19
+ - https://setuptools.pypa.io/en/latest/userguide/entry_point.html
20
+ - https://pip.pypa.io/en/stable/reference/pip_install
21
+ """
22
+
23
+ import argparse
24
+ import logging
25
+ import sys
26
+
27
+ from digraphx import __version__
28
+
29
+ __author__ = "Wai-Shing Luk"
30
+ __copyright__ = "Wai-Shing Luk"
31
+ __license__ = "MIT"
32
+
33
+ _logger = logging.getLogger(__name__)
34
+
35
+
36
+ # ---- Python API ----
37
+ # The functions defined in this section can be imported by users in their
38
+ # Python scripts/interactive interpreter, e.g. via
39
+ # `from digraphx.skeleton import fib`,
40
+ # when using this Python module as a library.
41
+
42
+
43
+ def fib(n):
44
+ """Fibonacci example function
45
+
46
+ Args:
47
+ n (int): integer
48
+
49
+ Returns:
50
+ int: n-th Fibonacci number
51
+ """
52
+ assert n > 0
53
+ a, b = 1, 1
54
+ for _i in range(n - 1):
55
+ a, b = b, a + b
56
+ return a
57
+
58
+
59
+ # ---- CLI ----
60
+ # The functions defined in this section are wrappers around the main Python
61
+ # API allowing them to be called directly from the terminal as a CLI
62
+ # executable/script.
63
+
64
+
65
+ def parse_args(args):
66
+ """Parse command line parameters
67
+
68
+ Args:
69
+ args (List[str]): command line parameters as list of strings
70
+ (for example ``["--help"]``).
71
+
72
+ Returns:
73
+ :obj:`argparse.Namespace`: command line parameters namespace
74
+ """
75
+ parser = argparse.ArgumentParser(description="Just a Fibonacci demonstration")
76
+ parser.add_argument(
77
+ "--version",
78
+ action="version",
79
+ version=f"digraphx {__version__}",
80
+ )
81
+ parser.add_argument(dest="n", help="n-th Fibonacci number", type=int, metavar="INT")
82
+ parser.add_argument(
83
+ "-v",
84
+ "--verbose",
85
+ dest="loglevel",
86
+ help="set loglevel to INFO",
87
+ action="store_const",
88
+ const=logging.INFO,
89
+ )
90
+ parser.add_argument(
91
+ "-vv",
92
+ "--very-verbose",
93
+ dest="loglevel",
94
+ help="set loglevel to DEBUG",
95
+ action="store_const",
96
+ const=logging.DEBUG,
97
+ )
98
+ return parser.parse_args(args)
99
+
100
+
101
+ def setup_logging(loglevel):
102
+ """Setup basic logging
103
+
104
+ Args:
105
+ loglevel (int): minimum loglevel for emitting messages
106
+ """
107
+ logformat = "[%(asctime)s] %(levelname)s:%(name)s:%(message)s"
108
+ logging.basicConfig(
109
+ level=loglevel, stream=sys.stdout, format=logformat, datefmt="%Y-%m-%d %H:%M:%S"
110
+ )
111
+
112
+
113
+ def main(args):
114
+ """Wrapper allowing :func:`fib` to be called with string arguments in a CLI fashion
115
+
116
+ Instead of returning the value from :func:`fib`, it prints the result to the
117
+ ``stdout`` in a nicely formatted message.
118
+
119
+ Args:
120
+ args (List[str]): command line parameters as list of strings
121
+ (for example ``["--verbose", "42"]``).
122
+ """
123
+ args = parse_args(args)
124
+ setup_logging(args.loglevel)
125
+ _logger.debug("Starting crazy calculations...")
126
+ print(f"The {args.n}-th Fibonacci number is {fib(args.n)}")
127
+ _logger.info("Script ends here")
128
+
129
+
130
+ def run():
131
+ """Calls :func:`main` passing the CLI arguments extracted from :obj:`sys.argv`
132
+
133
+ This function can be used as entry point to create console scripts with setuptools.
134
+ """
135
+ main(sys.argv[1:])
136
+
137
+
138
+ if __name__ == "__main__":
139
+ # ^ This is a guard statement that will prevent the following code from
140
+ # being executed in the case someone imports this file instead of
141
+ # executing it as a script.
142
+ # https://docs.python.org/3/library/__main__.html
143
+
144
+ # After installing your project with pip, users can also run your Python
145
+ # modules as scripts via the ``-m`` flag, as defined in PEP 338::
146
+ #
147
+ # python -m digraphx.skeleton 42
148
+ #
149
+ run()
@@ -0,0 +1,79 @@
1
+ """
2
+ TinyDiGraph
3
+
4
+ This code defines a custom graph data structure called TinyDiGraph, which is designed to be a lightweight and efficient implementation of a directed graph. The purpose of this code is to provide a simple way to create and manipulate directed graphs, particularly for cases where performance and memory efficiency are important.
5
+
6
+ The main input for this code is the number of nodes in the graph, which is set when initializing the graph using the init_nodes method. The code doesn't directly produce any output, but it provides methods to add edges, count nodes and edges, and iterate through the graph's structure.
7
+
8
+ TinyDiGraph achieves its purpose by subclassing from DiGraphAdapter, which in turn inherits from NetworkX's DiGraph class. This allows TinyDiGraph to leverage existing graph functionality while customizing certain aspects for efficiency. The key feature of TinyDiGraph is its use of a custom data structure called MapAdapter (likely a list-based dictionary) to store node and edge information.
9
+
10
+ The code implements several important methods:
11
+
12
+ 1. cheat_node_dict and cheat_adjlist_outer_dict: These methods create MapAdapter objects to store node and edge information efficiently.
13
+ 2. init_nodes: This method initializes the graph with a specified number of nodes, setting up the necessary data structures.
14
+
15
+ The main logic flow of the code is as follows:
16
+
17
+ 1. Define the TinyDiGraph class with custom node and edge storage methods.
18
+ 2. Provide a method to initialize the graph with a given number of nodes.
19
+ 3. Set up the graph structure using MapAdapter objects for efficient storage and access.
20
+
21
+ At the end of the file, there's a small example of how to use TinyDiGraph. It creates a graph with 1000 nodes, adds an edge, and then demonstrates how to iterate through the graph and access its properties.
22
+
23
+ The code also includes a brief demonstration of the MapAdapter data structure, showing how it can be used as an efficient list-like dictionary.
24
+
25
+ Overall, this code provides a foundation for working with directed graphs in a memory-efficient manner, which could be particularly useful for large graphs or in situations where performance is critical.
26
+ """
27
+
28
+ import networkx as nx
29
+ from mywheel.map_adapter import MapAdapter
30
+
31
+
32
+ class DiGraphAdapter(nx.DiGraph):
33
+ def items(self):
34
+ return self.adjacency()
35
+
36
+
37
+ class TinyDiGraph(DiGraphAdapter):
38
+ num_nodes = 0
39
+
40
+ def cheat_node_dict(self):
41
+ return MapAdapter([dict() for _ in range(self.num_nodes)])
42
+
43
+ def cheat_adjlist_outer_dict(self):
44
+ return MapAdapter([dict() for _ in range(self.num_nodes)])
45
+
46
+ node_dict_factory = cheat_node_dict
47
+ adjlist_outer_dict_factory = cheat_adjlist_outer_dict
48
+
49
+ def init_nodes(self, n: int):
50
+ """
51
+ The function initializes the number of nodes, a dictionary for nodes, and dictionaries for
52
+ adjacency and predecessor lists.
53
+
54
+ :param n: The parameter `n` represents the number of nodes in the graph
55
+ :type n: int
56
+ """
57
+ self.num_nodes = n
58
+ self._node = self.cheat_node_dict()
59
+ self._adj = self.cheat_adjlist_outer_dict()
60
+ self._pred = self.cheat_adjlist_outer_dict()
61
+
62
+
63
+ if __name__ == "__main__":
64
+ gr = TinyDiGraph()
65
+ gr.init_nodes(1000)
66
+ gr.add_edge(2, 1)
67
+ print(gr.number_of_nodes())
68
+ print(gr.number_of_edges())
69
+
70
+ for utx in gr:
71
+ for vtx in gr.neighbors(utx):
72
+ print(f"{utx}, {vtx}")
73
+
74
+ a = MapAdapter([0] * 8)
75
+ for i in a:
76
+ a[i] = i * i
77
+ for i, vtx in a.items():
78
+ print(f"{i}: {vtx}")
79
+ print(3 in a)
@@ -0,0 +1,56 @@
1
+ Metadata-Version: 2.4
2
+ Name: digraphx
3
+ Version: 0.1
4
+ Summary: Network Optimization Python Code
5
+ Home-page: https://github.com/luk036/digraphx
6
+ Author: Wai-Shing Luk
7
+ Author-email: luk036@gmail.com
8
+ License: MIT
9
+ Project-URL: Documentation, https://pyscaffold.org/
10
+ Platform: any
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Programming Language :: Python
13
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
14
+ License-File: LICENSE.txt
15
+ Requires-Dist: importlib-metadata; python_version < "3.9"
16
+ Requires-Dist: networkx
17
+ Provides-Extra: testing
18
+ Requires-Dist: setuptools; extra == "testing"
19
+ Requires-Dist: pytest; extra == "testing"
20
+ Requires-Dist: pytest-cov; extra == "testing"
21
+ Requires-Dist: networkx; extra == "testing"
22
+ Dynamic: license-file
23
+
24
+ <!-- These are examples of badges you might want to add to your README:
25
+ please update the URLs accordingly
26
+
27
+ [![Built Status](https://api.cirrus-ci.com/github/<USER>/digraphx.svg?branch=main)](https://cirrus-ci.com/github/<USER>/digraphx)
28
+ [![ReadTheDocs](https://readthedocs.org/projects/digraphx/badge/?version=latest)](https://digraphx.readthedocs.io/en/stable/)
29
+ [![Coveralls](https://img.shields.io/coveralls/github/<USER>/digraphx/main.svg)](https://coveralls.io/r/<USER>/digraphx)
30
+ [![PyPI-Server](https://img.shields.io/pypi/v/digraphx.svg)](https://pypi.org/project/digraphx/)
31
+ [![Conda-Forge](https://img.shields.io/conda/vn/conda-forge/digraphx.svg)](https://anaconda.org/conda-forge/digraphx)
32
+ [![Monthly Downloads](https://pepy.tech/badge/digraphx/month)](https://pepy.tech/project/digraphx)
33
+ [![Twitter](https://img.shields.io/twitter/url/http/shields.io.svg?style=social&label=Twitter)](https://twitter.com/digraphx)
34
+ -->
35
+
36
+ [![Project generated with PyScaffold](https://img.shields.io/badge/-PyScaffold-005CA0?logo=pyscaffold)](https://pyscaffold.org/)
37
+ [![Documentation Status](https://readthedocs.org/projects/digraphx/badge/?version=latest)](https://digraphx.readthedocs.io/en/latest/?badge=latest)
38
+ [![codecov](https://codecov.io/gh/luk036/digraphx/branch/main/graph/badge.svg?token=U7PKg0lceH)](https://codecov.io/gh/luk036/digraphx)
39
+
40
+ # 🔀 digraphx
41
+
42
+ > Directed Graph X in Python
43
+
44
+ A longer description of your project goes here...
45
+
46
+ ## Dependencies
47
+
48
+ - [luk036/mywheel](https://github.com/luk036/mywheel)
49
+ - networkx
50
+
51
+ <!-- pyscaffold-notes -->
52
+
53
+ ## 👉 Note
54
+
55
+ This project has been set up using PyScaffold 4.5. For details and usage
56
+ information on PyScaffold see https://pyscaffold.org/.
@@ -0,0 +1,14 @@
1
+ digraphx/__init__.py,sha256=ethe4DV1CwbXvO2DSjEzt5SL_7jt2fxQnF4RPgRgTDI,577
2
+ digraphx/max_cycle_ratio.tpy,sha256=e3GWroIm17nNEmVhjJ90ozCeRs5RhpeKkBkyACSwfSc,6623
3
+ digraphx/min_cycle_ratio.py,sha256=L6VzD2mPS7kcKBWQL-THCCEF-Bq39gEzMiccM2qs27g,9200
4
+ digraphx/min_parmetric_q.py,sha256=k-E77T1WB-FkCRadv6dtsBTOc3CzDujsLQI24W5LrPc,7120
5
+ digraphx/neg_cycle.py,sha256=H1IAMjk1M6Z5y92Ct14a_lQsVWzMafR0yPyoYiveK9M,11592
6
+ digraphx/neg_cycle_q.py,sha256=kHinHqeKkeRHVDo0l3B6YJAGt_so1aPHbMwf4isvy64,15876
7
+ digraphx/parametric.py,sha256=3TM9Sppy62b8mkxFEqoS4uUu9FF8V23CP9WOEu2eFYg,6384
8
+ digraphx/skeleton.py,sha256=uQaIAFPqpGOAEfyndv4qjIj1SSJk_P_gP2Rrk4BKGSQ,4219
9
+ digraphx/tiny_digraph.py,sha256=_Y9k58HleY5kso6rgo540ETYkM3hmtDNEPD5DmY64ns,3577
10
+ digraphx-0.1.dist-info/licenses/LICENSE.txt,sha256=MyvlyWiWdJ3ZpAOA4wziXlNa9Hbx8XnEL1Aztt9F7W8,1080
11
+ digraphx-0.1.dist-info/METADATA,sha256=CTlcQ9wPwvM4NN5YhW3kdu87OHT9Evi4voFHigbIbD4,2456
12
+ digraphx-0.1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
13
+ digraphx-0.1.dist-info/top_level.txt,sha256=ZrZmsCjPDRkUco9-v0BMtqpg7GXOF8g5ZCETmPlU5oE,9
14
+ digraphx-0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (78.1.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Wai-Shing Luk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ digraphx