├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── SECURITY.md ├── SUPPORT.md ├── graphing ├── __init__.py ├── decision_dag │ └── decision_dag.py ├── special_graphs │ ├── __init__.py │ ├── bipartite │ │ └── marginal_matching.py │ ├── directed_graph │ │ └── transitive_closure.py │ ├── neural_trigraph │ │ └── neural_trigraph.py │ └── neurograph │ │ ├── __init__.py │ │ ├── find_path.py │ │ ├── flow_graph.py │ │ ├── path_cover.py │ │ └── toy_graph.py ├── toy_graphs │ └── ukraine.py ├── traversal │ ├── __init__.py │ ├── clr_traversal.py │ └── walks.py └── use_cases │ ├── __init__.py │ └── find_faulty_network_link.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 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 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Project 2 | NOTE: We are in the process of migrating the code from here: https://github.com/ryu577/graphing. Until you see this message, please use that repository. 3 | 4 | > This repo has been populated by an initial template to help get you started. Please 5 | > make sure to update the content to build a great experience for community-building. 6 | 7 | As the maintainer of this project, please make a few updates: 8 | 9 | - Improving this README.MD file to provide a great experience 10 | - Updating SUPPORT.MD with content about this project's support experience 11 | - Understanding the security reporting process in SECURITY.MD 12 | - Remove this section from the README 13 | 14 | # Usage 15 | To install the library on your local machine, clone it and run from the base directory: 16 | 17 | > python setup.py install 18 | 19 | Then, try to run the following sample code: 20 | 21 | > from graphing.special_graphs.neural_trigraph.path_cover import min_cover_trigraph 22 | > 23 | > from graphing.special_graphs.neural_trigraph.rand_graph import * 24 | > ## Generate a random neural trigraph. Here, it is two sets of edges between layers 1 and 2 (edges1) and layers 2 and 3 (edges2) 25 | > edges1, edges2 = neur_trig_edges(7, 3, 7, shuffle_p=.05) 26 | > ## Find the full-path cover for this neural trigraph. 27 | > paths1 = min_cover_trigraph(edges1, edges2) 28 | > 29 | > print(paths1) 30 | 31 | 32 | ## Contributing 33 | 34 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 35 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 36 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 37 | 38 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 39 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 40 | provided by the bot. You will only need to do this once across all repos using our CLA. 41 | 42 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 43 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 44 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 45 | 46 | ## Trademarks 47 | 48 | This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft 49 | trademarks or logos is subject to and must follow 50 | [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). 51 | Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. 52 | Any use of third-party trademarks or logos are subject to those third-party's policies. 53 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # TODO: The maintainer of this repo has not yet edited this file 2 | 3 | **REPO OWNER**: Do you want Customer Service & Support (CSS) support for this product/project? 4 | 5 | - **No CSS support:** Fill out this template with information about how to file issues and get help. 6 | - **Yes CSS support:** Fill out an intake form at [aka.ms/onboardsupport](https://aka.ms/onboardsupport). CSS will work with/help you to determine next steps. 7 | - **Not sure?** Fill out an intake as though the answer were "Yes". CSS will help you decide. 8 | 9 | *Then remove this first heading from this SUPPORT.MD file before publishing your repo.* 10 | 11 | # Support 12 | 13 | ## How to file issues and get help 14 | 15 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 16 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 17 | feature request as a new Issue. 18 | 19 | For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE 20 | FOR HOW TO ENGAGE REPO OWNERS OR COMMUNITY FOR HELP. COULD BE A STACK OVERFLOW TAG OR OTHER 21 | CHANNEL. WHERE WILL YOU HELP PEOPLE?**. 22 | 23 | ## Microsoft Support Policy 24 | 25 | Support for this **PROJECT or PRODUCT** is limited to the resources listed above. 26 | -------------------------------------------------------------------------------- /graphing/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /graphing/decision_dag/decision_dag.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from collections import defaultdict 3 | import networkx as nx 4 | from pyvis.network import Network 5 | import matplotlib.pyplot as plt 6 | import re 7 | 8 | # Node for decision DAG 9 | class DAGNode(object): 10 | def __init__(self, key1='a', adj_lst=[]): 11 | self.nxt = None 12 | self.nxt_ix = 0 13 | self.adj_lst = adj_lst 14 | 15 | def get_nxt(self, icm_cand): 16 | return 0 17 | 18 | 19 | class RgxLstNode(DAGNode): 20 | def __init__(self, regx_lst, prms, key1, adj_lst=[]): 21 | self.regx_lst = regx_lst 22 | self.prms = prms 23 | super().__init__(key1=key1, adj_lst=adj_lst) 24 | 25 | def get_nxt(self, icm_cand): 26 | ix = 0 27 | self.nxt = self.adj_lst[0] 28 | for rgx in self.regx_lst: 29 | if re.match(rgx, icm_cand.signal): 30 | self.taus = self.prms[ix] 31 | self.nxt_ix = 1 32 | self.nxt = self.adj_lst[1] 33 | ix += 1 34 | return self.taus 35 | 36 | 37 | class IfEsclToNode(DAGNode): 38 | def get_nxt(self, icm_cand): 39 | if icm_cand.escl_to is None: 40 | icm_cand.assign_to = icm_cand.component 41 | else: 42 | icm_cand.assign_to = icm_cand.escl_to 43 | 44 | 45 | class SevSubDAGNode(DAGNode): 46 | def __init__(self, key1="SevNode"): 47 | super().__init__(key1) 48 | 49 | def get_nxt(self, icm_cand, taus=None): 50 | if taus is not None: 51 | tau1, tau2, tau3 = taus 52 | else: 53 | tau1, tau2, tau3 = .01,1,4 54 | sev = 0 55 | # This logic can be replaced by a 3-node decision tree. 56 | if icm_cand.p_val < tau1: 57 | if icm_cand.ctrl_nodes < tau2: 58 | if icm_cand.trt_nodes > tau3: 59 | sev = 3 60 | else: 61 | sev = 4 62 | else: 63 | sev = 4 64 | else: 65 | sev = 5 66 | icm_cand.sev = sev 67 | self.nxt_ix = sev - 3 68 | if self.adj_lst is not None and self.nxt_ix < len(self.adj_lst): 69 | self.nxt = self.adj_lst[self.nxt_ix] 70 | 71 | 72 | class DecisionDAG(): 73 | def __init__(self, mapper, adj, strt_key='CmpDeny'): 74 | self.mapper = mapper 75 | self.adj = adj 76 | self.strt_key = strt_key 77 | 78 | def walk_dag(self, icm_cand): 79 | node = self.mapper[self.strt_key] 80 | node.get_nxt(icm_cand) 81 | taus = None 82 | while node.nxt is not None: 83 | node = node.nxt 84 | if taus is None: 85 | taus = node.get_nxt(icm_cand) 86 | else: 87 | taus = node.get_nxt(icm_cand, taus) 88 | # By now, the relevant properties 89 | # of the ICM are populated. 90 | self.icm = icm_cand 91 | 92 | 93 | # regex for excluding xyzf but including xxx*bc: "^(?!.*xyzf).*xxx.*bc$" 94 | # Instead of this, just match "xyzf" via deny list ".*xyzf" 95 | # and then xxx*bc via allow list: ".*xxx.*bc" 96 | # Further, if you wanted to allow ".*xxx.*bc1", ".*xxx.*bc2", 97 | # you would have to append the exclusion ^(?!.*xyzf)" in both places. 98 | def tst_walk(): 99 | deny_lst = [".*a1", ".*n1"] 100 | deny_prms = [(.01, 1, 4), (.01, 1, 4)] 101 | allow_lst = [".*xyz.*xxx"] 102 | allow_prms = [(.01, 1, 4)] 103 | mapper = { 104 | 'CmpDeny': RgxLstNode(deny_lst, deny_prms, 'CmpDeny'), 105 | 'CmpAllow': RgxLstNode(allow_lst, allow_prms, 'CmpAllow'), 106 | 'GlblDeny': RgxLstNode(deny_lst, deny_prms, 'GlblDeny'), 107 | 'GlblAllow': RgxLstNode(allow_lst, allow_prms, 'GlblAllow'), 108 | 'HasEsclTo': IfEsclToNode('HasEsclTo'), 109 | 'SevDAG1': SevSubDAGNode('SevDAG1'), 110 | 'SevDAG2': SevSubDAGNode('SevDAG2') 111 | } 112 | adj = defaultdict(list) 113 | adj['CmpDeny'] = ['CmpAllow', 'SevDAG1'] 114 | adj['CmpAllow'] = ['GlblDeny', 'SevDAG2'] 115 | adj['GlblDeny'] = ['GlblAllow', 'SevDAG1'] 116 | adj['GlblAllow'] = ['SevDAG2', 'SevDAG1'] 117 | adj['SevDAG2'] = ['HasEsclTo'] 118 | for k in adj: 119 | mapper[k].adj_lst = [mapper[kk] for kk in adj[k]] 120 | strt_key = 'CmpDeny' 121 | dd = DecisionDAG(mapper, adj, strt_key) 122 | icm_cand = ICMCandidate() 123 | #return icm_cand, mapper, adj, dd 124 | dd.walk_dag(icm_cand) 125 | return dd 126 | 127 | 128 | class ICMCandidate(): 129 | def __init__(self, 130 | signal="fault__nn", 131 | component="agent", 132 | escl_to=None, 133 | p_val=1e-5, 134 | trt_nodes=5, 135 | ctrl_nodes=0): 136 | self.signal = signal 137 | self.component = component 138 | self.escl_to = escl_to 139 | self.p_val=p_val 140 | self.trt_nodes = trt_nodes 141 | self.ctrl_nodes = ctrl_nodes 142 | 143 | 144 | def tst_plot(): 145 | g = nx.DiGraph() 146 | g.add_edge('CmpDeny', 'CmpAllow') 147 | g.add_edge('CmpDeny', 'SevDAG1') 148 | g.add_edge('CmpAllow', 'GlblDeny') 149 | g.add_edge('CmpAllow', 'SevDAG2') 150 | g.add_edge('GlblDeny', 'GlblAllow') 151 | g.add_edge('GlblDeny', 'SevDAG1') 152 | g.add_edge('GlblAllow', 'SevDAG2') 153 | g.add_edge('GlblAllow', 'SevDAG1') 154 | g.add_edge('SevDAG2', 'HasEsclTo') 155 | nx.draw(g, with_labels=True) 156 | net = Network(directed=True) 157 | net.from_nx(g) 158 | net.show("ex.html") 159 | #plt.show() 160 | return g 161 | 162 | 163 | def tst_plot1(): 164 | g = tst_plot() 165 | net = Network(directed =True) 166 | ix_2_key = {0: 'CmpDeny', 1: 'CmpAllow', 2: 'SevDAG1', 167 | 3: 'GlblDeny', 4: 'SevDAG2', 168 | 5: 'GlblAllow', 6: 'HasEsclTo'} 169 | key_2_ix = {} 170 | for k in ix_2_key.keys(): 171 | key_2_ix[ix_2_key[k]] = k 172 | if k == 0: 173 | net.add_node(k, label=ix_2_key[k], color="green", pos="34,12!") 174 | elif ix_2_key[k] == 'HasEsclTo' or ix_2_key[k] == 'SevDAG1': 175 | net.add_node(k, label=ix_2_key[k], color="red") 176 | else: 177 | net.add_node(k, label=ix_2_key[k]) 178 | for e in g.edges: 179 | net.add_edge(key_2_ix[e[0]],key_2_ix[e[1]], label="0") 180 | net.show('ex.html') 181 | 182 | -------------------------------------------------------------------------------- /graphing/special_graphs/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /graphing/special_graphs/bipartite/marginal_matching.py: -------------------------------------------------------------------------------- 1 | import networkx as nx 2 | import numpy as np 3 | 4 | 5 | def get_schedule(probs_left, probs_right, edges, num_nodes=20): 6 | source = 0 7 | dest = np.max(edges)+1 8 | left_max_ix = max(edges[::, 0]) 9 | right_max_ix = max(edges[::, 1]) 10 | g = nx.DiGraph() 11 | 12 | for v in probs_left.keys(): 13 | cap = int(probs_left[v]*num_nodes) 14 | g.add_edge(source, v, capacity=cap, weight=1/(cap+1e-3)) 15 | 16 | for u, v in edges: 17 | g.add_edge(u, v, capacity=np.inf, weight=1) 18 | 19 | for v in probs_right.keys(): 20 | cap = int(probs_right[v]*num_nodes) 21 | g.add_edge(v, dest, capacity=cap, weight=1/(cap+1e-3)) 22 | 23 | flowed = 0 24 | while flowed < num_nodes: 25 | # print("Now running networkx max-flow-min-cost " + str(right_max_ix)) 26 | # res_dict = nx.max_flow_min_cost(g, source, dest) 27 | res_val, res_dict = nx.maximum_flow(g, source, dest) 28 | flowed = res_val 29 | if np.random.uniform() > 0.5: 30 | h = np.random.choice(left_max_ix) + 1 31 | g[0][h]['capacity'] += 1 32 | else: 33 | v = np.random.choice(np.arange(left_max_ix+1, right_max_ix+1)) 34 | g[v][dest]['capacity'] += 1 35 | 36 | return res_dict 37 | 38 | 39 | def tst(): 40 | probs_left = {1: .3333, 2: .33333, 3: .33333} 41 | probs_right = {4: .25, 5: .25, 6: .25, 7: .25} 42 | edges = np.array([ 43 | [1, 4], 44 | [1, 5], 45 | [2, 4], 46 | [3, 5], 47 | [3, 6], 48 | [3, 7] 49 | ]) 50 | res = get_schedule(probs_left, probs_right, edges) 51 | return res 52 | 53 | 54 | def score(flow_dict, probs_left, probs_right): 55 | dest = max(probs_right.keys()) + 1 56 | source_flows = flow_dict[0] 57 | total_flow = sum(source_flows.values()) 58 | summ = 0 59 | for k in probs_left.keys(): 60 | summ += (source_flows[k]/total_flow-probs_left[k])**2 61 | for k in probs_right.keys(): 62 | summ += (probs_right[k] - flow_dict[k][dest]/total_flow)**2 63 | return summ 64 | 65 | 66 | def best_schedule(n_iter=100): 67 | probs_left = {1: .3333, 2: .33333, 3: .33333} 68 | probs_right = {4: .25, 5: .15, 6: .15, 7: .45} 69 | edges = np.array([ 70 | [1, 4], 71 | [1, 5], 72 | [2, 4], 73 | [3, 5], 74 | [3, 6], 75 | [3, 7] 76 | ]) 77 | min_score = np.inf 78 | best_dict = {} 79 | for _ in range(n_iter): 80 | res = get_schedule(probs_left, probs_right, edges) 81 | candidate_score = score(res, probs_left, probs_right) 82 | if candidate_score < min_score: 83 | min_score = candidate_score 84 | best_dict = res 85 | return best_dict 86 | -------------------------------------------------------------------------------- /graphing/special_graphs/directed_graph/transitive_closure.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | import numpy as np 3 | 4 | 5 | class DGraph(): 6 | def __init__(self, num_vertices): 7 | self.num_vertices = num_vertices 8 | self.graph = defaultdict(list) 9 | self.tc_graph = defaultdict(list) 10 | # Transitive closure. 11 | self.tc = np.zeros((num_vertices, num_vertices)) 12 | 13 | def add_edge(self, u, v): 14 | self.graph[u].append(v) 15 | 16 | def dfs_traverse(self, s, v): 17 | self.tc[s][v] = 1 18 | self.tc_graph[s].append(v) 19 | 20 | for i in self.graph[v]: 21 | if self.tc[s][i] < 1: 22 | self.dfs_traverse(s, i) 23 | 24 | def transitive_closure(self): 25 | for i in range(self.num_vertices): 26 | self.dfs_traverse(i, i) 27 | 28 | 29 | def transitive_closure(g): 30 | ''' 31 | Parameters: 32 | g (dict), a DAG with adjacent list representation 33 | Return: 34 | trans_path(dict), where key = vertice v, value = a dict maps reachable 35 | nodes u to a path of v to u 36 | trans_dict(dict), the adjacent list representation of the transitive 37 | closure of g 38 | ''' 39 | trans_path = {} 40 | trans_dict = {} 41 | for v in g: 42 | transitive_closure_helper(g, trans_path, v) 43 | 44 | for v in trans_path: 45 | reachables = [] 46 | reachable_dict = trans_path[v] 47 | for u in reachable_dict: 48 | reachables.append(u) 49 | trans_dict[v] = reachables 50 | return trans_path, trans_dict 51 | 52 | 53 | def transitive_closure_helper(g, trans_path, v): 54 | ''' 55 | Helper function of the 'transitive_closure' 56 | Parameters: 57 | g (dict), a DAG with adjacent list representation 58 | trans_path(dict), where key = vertice v, value = a dict maps reachable 59 | nodes u to a path of v to u 60 | v: a vertex v 61 | Return: 62 | trans_path(dict) 63 | ''' 64 | if v in trans_path: 65 | return trans_path 66 | if len(g[v]) == 0: 67 | trans_path[v] = {} 68 | return trans_path 69 | nbrs = g[v] 70 | v_dict = {} 71 | for u in nbrs: 72 | recur = transitive_closure_helper(g, trans_path, u) 73 | u_dict = recur[u] 74 | for node in u_dict: 75 | if node not in v_dict: 76 | # TODO: O(n), will fix later to amortized O(1). 77 | v_dict[node] = [v] + u_dict[node] 78 | v_dict[u] = [v, u] 79 | trans_path[v] = v_dict 80 | return trans_path 81 | 82 | 83 | def tst(): 84 | g = DGraph(4) 85 | g.add_edge(0, 1) 86 | g.add_edge(0, 2) 87 | g.add_edge(1, 2) 88 | g.add_edge(2, 0) 89 | g.add_edge(2, 3) 90 | g.add_edge(3, 3) 91 | g.transitive_closure() 92 | print(g.tc) 93 | 94 | 95 | if __name__ == "__main__": 96 | print("Running tests for transitive closure.") 97 | tst() 98 | 99 | # References 100 | # [1] https://www.geeksforgeeks.org/transitive-closure-of-a-graph-using-dfs/ 101 | # [2] https://www.cs.princeton.edu/courses/archive/spr03/cs226/lectures/digraph.4up.pdf 102 | -------------------------------------------------------------------------------- /graphing/special_graphs/neural_trigraph/neural_trigraph.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import networkx as nx 3 | from networkx.algorithms.flow import maximum_flow 4 | from graphing.special_graphs.neural_trigraph.central_vert import NeuralTriGraphCentralVert 5 | import re 6 | import random 7 | import collections 8 | 9 | 10 | class NeuralTriGraph(): 11 | """ 12 | A neural tri-grpah is a special case of a tri-partite 13 | graph. In it, the vertices can be segregated into three 14 | layers. However unlike a tri-partite graph, connections 15 | exist only between successive layers (1 and 2; 2 and 3). 16 | Such graphs describe the layers of a neural network; 17 | hence the name. 18 | """ 19 | def __init__(self, left_edges, right_edges): 20 | self.left_edges = left_edges 21 | self.right_edges = right_edges 22 | self.vertices = set(left_edges.flatten())\ 23 | .union(set(right_edges.flatten())) 24 | self.layer_1 = set(left_edges[:,0]) 25 | self.layer_2 = set(left_edges[:,1]) 26 | self.layer_3 = set(right_edges[:,1]) 27 | self.layer_1_size = len(self.layer_1) 28 | self.layer_2_size = len(self.layer_2) 29 | self.layer_3_size = len(self.layer_3) 30 | self.layer_1_dict = {} 31 | for e in left_edges: 32 | if e[0] not in self.layer_1_dict: 33 | self.layer_1_dict[e[0]] = set([e[1]]) 34 | else: 35 | self.layer_1_dict[e[0]].add(e[1]) 36 | self.layer_3_dict = {} 37 | for e in right_edges: 38 | if e[1] not in self.layer_3_dict: 39 | self.layer_3_dict[e[1]] = set([e[0]]) 40 | else: 41 | self.layer_3_dict[e[1]].add(e[0]) 42 | self.central_vert_dict = create_central_vert_dict(left_edges,\ 43 | right_edges) 44 | 45 | def create_bipartite_graph(self): 46 | self.flow_graph = nx.DiGraph() 47 | for ed in self.left_edges: 48 | ## The vertices from which flow travels only out. 49 | v1 = "out_layer0_elem" + str(ed[0]) 50 | v2 = "in_layer1_elem" + str(ed[1]) 51 | self.flow_graph.add_edge(v1,v2,capacity=1,weight=1) 52 | for ed in self.right_edges: 53 | v1 = "out_layer1_elem" + str(ed[0]) 54 | v2 = "in_layer2_elem" + str(ed[1]) 55 | self.flow_graph.add_edge(v1,v2,capacity=1,weight=1) 56 | for k in self.central_vert_dict.keys(): 57 | for l in self.central_vert_dict[k].l_edges: 58 | for r in self.central_vert_dict[k].r_edges: 59 | v1 = "out_layer0_elem" + str(l) 60 | v2 = "in_layer2_elem" + str(r) 61 | self.flow_graph.add_edge(v1,v2,capacity=1,weight=1) 62 | v1="source" 63 | for e in self.layer_1: 64 | v2 = "out_layer0_elem" + str(e) 65 | self.flow_graph.add_edge(v1,v2,capacity=1,weight=1) 66 | for e in self.layer_2: 67 | v2 = "out_layer1_elem" + str(e) 68 | self.flow_graph.add_edge(v1,v2,capacity=1,weight=1) 69 | for e in self.layer_3: 70 | v2 = "out_layer2_elem" + str(e) 71 | self.flow_graph.add_edge(v1,v2,capacity=1,weight=1) 72 | v2="sink" 73 | for e in self.layer_1: 74 | v1 = "in_layer0_elem" + str(e) 75 | self.flow_graph.add_edge(v1,v2,capacity=1,weight=1) 76 | for e in self.layer_2: 77 | v1 = "in_layer1_elem" + str(e) 78 | self.flow_graph.add_edge(v1,v2,capacity=1,weight=1) 79 | for e in self.layer_3: 80 | v1 = "in_layer2_elem" + str(e) 81 | self.flow_graph.add_edge(v1,v2,capacity=1,weight=1) 82 | 83 | def determine_layer(self, ind): 84 | if ind < self.layer_1_size: 85 | return 0 86 | elif ind < self.layer_2_size: 87 | return 1 88 | else: 89 | return 2 90 | 91 | 92 | def create_central_vert_dict(edges1, edges2): 93 | vert_set = {} 94 | for e in edges1: 95 | if e[1] not in vert_set: 96 | tg = NeuralTriGraphCentralVert(e) 97 | vert_set[e[1]] = tg 98 | else: 99 | vert_set[e[1]].add(e) 100 | for e in edges2: 101 | if e[0] not in vert_set: 102 | tg = NeuralTriGraphCentralVert(e) 103 | vert_set[e[0]] = tg 104 | else: 105 | vert_set[e[0]].add(e) 106 | return vert_set 107 | 108 | 109 | def is_valid_3_neural(edges1, edges2): 110 | if min(edges2[::,1])-max(edges1[::,1])!=1: 111 | return False 112 | if min(edges1[::,1])-max(edges1[::,0])!=1: 113 | return False 114 | if min(edges2[::,1])-max(edges2[::,0])!=1: 115 | return False 116 | return True 117 | 118 | 119 | def tst1(): 120 | ## Test case-1 121 | edges1 = np.array([[1,4],[2,4],[2,5],[3,5]]) 122 | edges2 = np.array([[4,6],[4,7],[5,8]]) 123 | nu = NeuralTriGraph(edges1, edges2) 124 | nu.create_bipartite_graph() 125 | ##For debugging: 126 | [e for e in nu.flow_graph.edges] 127 | flow_val, flow_dict = nx.maximum_flow(nu.flow_graph, 'source', 'sink') 128 | #paths = max_matching_to_paths(flow_dict) 129 | 130 | ## Test case-2 131 | edges1 = np.array([[1,5],[2,5],[3,7],[4,6]]) 132 | edges2 = np.array([[5,8],[5,9],[5,10],[7,11],[6,11]]) 133 | 134 | -------------------------------------------------------------------------------- /graphing/special_graphs/neurograph/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /graphing/special_graphs/neurograph/find_path.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from collections import defaultdict 3 | from graphing.special_graphs.neural_trigraph.rand_graph import neur_trig_edges, rep_graph 4 | from graphing.special_graphs.neurograph.toy_graph import NeuralGraphVert, ToyGraphs 5 | from functools import cmp_to_key 6 | import time 7 | 8 | 9 | def dfs_search(g, v, t, visited_dict=set()): 10 | if v.key not in visited_dict: 11 | visited_dict.add(v.key) 12 | if v.key == t.key: 13 | return [] 14 | elif v.layer < t.layer: 15 | for u in g[v.key]: 16 | if u.key not in visited_dict: 17 | res = dfs_search(g, u, t, visited_dict) 18 | if res is not None: 19 | res.append(u.key) 20 | return res 21 | 22 | 23 | def dfs_targeted(g, v, t, path=[], visited_dict=set()): 24 | if v.key not in visited_dict: 25 | visited_dict.add(v.key) 26 | if v.key == t.key: 27 | return [] 28 | elif v.layer < t.layer: 29 | 30 | # Comparator for preferring vertices close in index to target. 31 | def compare(x, y): 32 | return abs(x.layer_ix - t.layer_ix)\ 33 | - abs(y.layer_ix - t.layer_ix) 34 | for u in sorted(g[v.key], key=cmp_to_key(compare)): 35 | if u.key not in visited_dict: 36 | res = dfs_targeted(g, u, t, path, visited_dict) 37 | if res is not None: 38 | res.append(u.key) 39 | return res 40 | 41 | 42 | def tst(): 43 | g = ToyGraphs.toy_graph_1() 44 | v1 = NeuralGraphVert(1, 1, 1) 45 | v7 = NeuralGraphVert(7, 3, 2) 46 | visited_dict = set() 47 | path = dfs_targeted(g, v1, v7, visited_dict=visited_dict) 48 | path.append(v1.key) 49 | path.reverse() 50 | print(path) 51 | print(visited_dict) 52 | 53 | 54 | def tst_dfs_vanilla(): 55 | g = ToyGraphs.toy_graph_2() 56 | v1 = NeuralGraphVert(1, 1, 1) 57 | v11 = NeuralGraphVert(11, 4, 4) 58 | visited_dict = set() 59 | path = dfs_search(g, v1, v11, visited_dict=visited_dict) 60 | path.append(v1.key) 61 | path.reverse() 62 | print(path) 63 | print(visited_dict) 64 | 65 | 66 | def tst_dfs_targeted(): 67 | g = ToyGraphs.toy_graph_2() 68 | v1 = NeuralGraphVert(1, 1, 1) 69 | v11 = NeuralGraphVert(11, 4, 4) 70 | visited_dict = set() 71 | path = dfs_targeted(g, v1, v11, visited_dict=visited_dict) 72 | path.append(v1.key) 73 | path.reverse() 74 | print(path) 75 | print(visited_dict) 76 | 77 | 78 | if __name__ == "__main__": 79 | start = time.time() 80 | tst_dfs_vanilla() 81 | end = time.time() 82 | print("Naive DFS took: " + str(end-start) + " secs") 83 | start = time.time() 84 | tst_dfs_targeted() 85 | end = time.time() 86 | print("Targeted DFS took: " + str(end-start) + " secs") 87 | 88 | 89 | 90 | # Demonstration of call-stack behavior and how None is returned 91 | # if nothing found. 92 | def fn1(a): 93 | if a == 1: 94 | return a 95 | else: 96 | return fn2(a) 97 | 98 | 99 | def fn2(a): 100 | if a == 1: 101 | return a 102 | 103 | 104 | def tst_call_stack(): 105 | a = fn1(2) 106 | print(a is None) 107 | 108 | 109 | # [1] https://stackoverflow.com/questions/29863851/python-stop-recursion-once-solution-is-found 110 | -------------------------------------------------------------------------------- /graphing/special_graphs/neurograph/flow_graph.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import graphing.special_graphs.neural_trigraph.toy_graphs as tg 3 | from copy import deepcopy 4 | 5 | 6 | class FlowGraph1(object): 7 | """ 8 | The purpose of this class is to convert 9 | a flow dictionary to a list of paths. 10 | """ 11 | def __init__(self, g): 12 | """ 13 | Initialize a flow graph dictionary so we 14 | can convert to a list of paths. 15 | args: 16 | g: A flow dictionary. 17 | """ 18 | self.g = g 19 | self.dest = max(list(g.keys()))+1 20 | self.paths = [] 21 | 22 | def dfs(self, u): 23 | if u == self.dest: 24 | self.paths.append(self.path) 25 | self.path = [] 26 | return 27 | self.path.append(u) 28 | # This is typically where the loop comes in 29 | # for traditional DFS. 30 | v = next(iter(self.g[u])) 31 | self.g[u][v] -= 1 32 | self.dfs(v) 33 | # Now remove redundant keys. 34 | if self.g[u][v] == 0: 35 | del self.g[u][v] 36 | 37 | def dfs_init(self): 38 | while len(self.g[0].keys()) > 0: 39 | for v in self.g[0].keys(): 40 | self.path = [] 41 | self.g[0][v] -= 1 42 | self.dfs(v) 43 | # Need to store a seperate array for keys 44 | # since dictionary can't be modified while 45 | # iterating on it. 46 | keyss = list(self.g[0].keys()) 47 | for v in keyss: 48 | if self.g[0][v] == 0: 49 | del self.g[0][v] 50 | 51 | 52 | def tst(gr_no=1): 53 | if gr_no == 1: 54 | g = deepcopy(tg.ToyGraph1.res) 55 | else: 56 | g = deepcopy(tg.ToyGraph2.res) 57 | fg = FlowGraph1(g) 58 | fg.dfs_init() 59 | return fg 60 | -------------------------------------------------------------------------------- /graphing/special_graphs/neurograph/path_cover.py: -------------------------------------------------------------------------------- 1 | import networkx as nx 2 | from networkx.algorithms import bipartite 3 | from graphing.special_graphs.neural_trigraph.path_cover \ 4 | import min_cover_trigraph, min_cover_trigraph_heuristic1 5 | from graphing.special_graphs.neural_trigraph.rand_graph import neur_trig_edges, rep_graph 6 | from graphing.special_graphs.directed_graph.transitive_closure import transitive_closure 7 | from graphing.special_graphs.neural_trigraph.neural_trigraph import is_valid_3_neural 8 | import random 9 | import time 10 | 11 | 12 | def create_path_cover(g): 13 | ''' 14 | idea: 15 | 1. create the bipartite graph. split each node v into two nodes v_top 16 | and v_bottom, where v_top connects with all outgoing edges of v, and 17 | v_bottom connects with all incoming edges of v. 18 | 2. use max matching in networkx to generate the matching 19 | 3. recover the path in the transitive closure graph using the 20 | bipartite graph matching 21 | 4. reference: https://towardsdatascience.com/solving-minimum-path-cover-on-a-dag-21b16ca11ac0 22 | Parameter: g (dict), the transitive closure of the DAG 23 | Return: the paths in the transitive closure (list of list) 24 | ''' 25 | # generate bipartite 26 | b = nx.Graph() 27 | top_nodes = [] 28 | bottom_nodes = [] 29 | # generate nodes 30 | for v in g: 31 | v_top = str(v) + "t" 32 | v_bottom = str(v) + "b" 33 | top_nodes.append(v_top) 34 | bottom_nodes.append(v_bottom) 35 | # generate edges 36 | edges = [] 37 | for v in g: 38 | nbrs = g[v] 39 | for u in nbrs: 40 | v_top = str(v) + "t" 41 | u_bottom = str(u) + "b" 42 | edges.append([v_top, u_bottom]) 43 | 44 | b.add_nodes_from(top_nodes, bipartite=0) 45 | b.add_nodes_from(bottom_nodes, bipartite=1) 46 | b.add_edges_from(edges) 47 | 48 | # calculate maximum cardinality matching 49 | # matching: a dict maps node to node (both ways, represent edges) 50 | matching = bipartite.matching.hopcroft_karp_matching(b, top_nodes) 51 | 52 | # generate paths from matching 53 | paths = [] 54 | visited_nodes = set() 55 | for v in sorted(g.keys()): 56 | path = [] 57 | if v not in visited_nodes: 58 | visited_nodes.add(v) 59 | path.append(v) 60 | v_top = str(v) + "t" 61 | while v_top in matching: 62 | u_bottom = matching[v_top] 63 | u = int(u_bottom[:-1]) 64 | path.append(u) 65 | visited_nodes.add(u) 66 | v_top = u_bottom[:-1] + "t" 67 | if len(path) > 0: 68 | paths.append(path) 69 | return paths 70 | 71 | 72 | def recover_path(g): 73 | ''' 74 | Recover the path in the original graph from the path in the transitive 75 | graph, using the "trans_path" dict 76 | 77 | Parameter: g, the original graph 78 | Return: the recovered path 79 | ''' 80 | original_paths = [] 81 | trans_path, trans_dict = transitive_closure(g) 82 | ret_paths = create_path_cover(trans_dict) 83 | for path in ret_paths: 84 | original_path = [] 85 | for i in range(len(path)-1): 86 | v = path[i] 87 | u = path[i+1] 88 | v_u_path = trans_path[v][u] 89 | original_path.extend(v_u_path[:-1]) 90 | original_path.append(path[-1]) 91 | original_paths.append(original_path) 92 | return original_paths 93 | 94 | 95 | def parent_dict(g): 96 | ''' 97 | Construct parent_dict: maps node v to node v's parent; 98 | if a node has no parent, maps to None 99 | Parameter: the original graph g 100 | Return: the parent dict 101 | ''' 102 | parent_dict = {} 103 | for v in g: 104 | nbrs = g[v] 105 | for u in nbrs: 106 | parent_dict[u] = v 107 | for v in g: 108 | if v not in parent_dict: 109 | parent_dict[v] = None 110 | return parent_dict 111 | 112 | 113 | def construct_complete_path(g, paths): 114 | ''' 115 | some of the original_paths may be incomplete. We want to construct the 116 | complete paths from the first layer to the last layer 117 | Parameters: 118 | g(dict), DAG 119 | paths (list of list): the incomplete paths returned from 'recover_path' 120 | Return: 121 | the complete paths (list of list) 122 | ''' 123 | # recover the head part 124 | parent = parent_dict(g) 125 | for i in range(len(paths)): 126 | path = paths[i] 127 | while parent[path[0]] is not None: 128 | path = [parent[path[0]]] + path 129 | paths[i] = path 130 | # recover the tail part 131 | for path in paths: 132 | while len(g[path[-1]]) > 0: 133 | path.append(g[path[-1]][0]) 134 | return paths 135 | 136 | 137 | def min_path_cover(g): 138 | ''' 139 | the main function; combines all previous functions 140 | parameter: g(dict), DAG 141 | return: the completed, recovered path of g 142 | ''' 143 | original_paths = recover_path(g) 144 | return construct_complete_path(g, original_paths) 145 | 146 | 147 | def graph_converter(edges1, edges2): 148 | ''' 149 | Parameters: edges1, edges2 (list of list), generated 150 | from the neur_trig_edges 151 | return: an adjacent list representation of the graph 152 | ''' 153 | g = {} 154 | vertices = set() 155 | for e in edges1: 156 | vertices.add(e[0]) 157 | for e in edges2: 158 | vertices.add(e[0]) 159 | vertices.add(e[1]) 160 | for v in vertices: 161 | g[v] = [] 162 | for e in edges1: 163 | g[e[0]].append(e[1]) 164 | for e in edges2: 165 | g[e[0]].append(e[1]) 166 | return g 167 | 168 | 169 | def check_path_cover(g, paths): 170 | ''' 171 | check if the generated paths have covered all vertices, and if the paths 172 | are valid paths 173 | Parameters: 174 | g (adjacent list), DAG 175 | paths (list of list), the result we obtained 176 | ''' 177 | parent = parent_dict(g) 178 | ret_vertices = set() 179 | for path in paths: 180 | for v in path: 181 | ret_vertices.add(v) 182 | assert len(ret_vertices) == len(g), "not all vertices are covered" 183 | flag = True 184 | for path in paths: 185 | for i in range(len(path)-1): 186 | current = path[i] 187 | next = path[i+1] 188 | if next not in g[current]: 189 | flag = False 190 | break 191 | last = path[-1] 192 | assert len(g[last]) == 0, "this is not a complete path " + str(path) 193 | assert parent[path[0]] is None, "this is not a complete path " + str(path) 194 | assert flag is True, "this is not a correct set of path" + paths 195 | 196 | 197 | # test case 7 198 | def neuro_trigraph_stack_test(left, center, right, rep): 199 | ''' 200 | Create test cases by stacking small neurotrigraphs together, repeating 201 | rep number of times 202 | Parameters: 203 | left (int): number of vertices on the left layer 204 | center (int): number of vertices in the middle layer 205 | right (int): number of vertices on the right layer 206 | rep (int): the number of times neurotrigraphs repeating 207 | ''' 208 | print("left: " + str(left*rep) + ", center:" + str(center*rep) + ", right:" + str(right*rep)) 209 | edges1, edges2 = rep_graph(left, right, center, rep) 210 | print("Generated the graph.") 211 | if not is_valid_3_neural(edges1, edges2): 212 | print("Invalid graph this time") 213 | return 214 | start = time.time() 215 | ans = min_cover_trigraph(edges1, edges2) 216 | end = time.time() 217 | print("Original took: " + str(end-start) + " secs") 218 | g = graph_converter(edges1, edges2) 219 | start = time.time() 220 | paths = min_path_cover(g) 221 | end = time.time() 222 | print("New method took: " + str(end-start) + " secs") 223 | check_path_cover(g, paths) 224 | print("Original method shows: " + str(len(ans)) + " paths.") 225 | print("New method shows: " + str(len(paths)) + " paths.") 226 | assert len(paths) == len(ans), "Number of paths incorrect. Correct number of paths is " + str(len(ans)) +\ 227 | "returned number of path is " + str(len(paths))\ 228 | + "\n edges1" + str(edges1) + "\n edge2" + str(edges2)\ 229 | + "\n g" + str(g) + "\n ans" + str(ans) + "\n paths" + str(paths) 230 | 231 | 232 | def tst_stack(): 233 | for i in range(100): 234 | left = int(random.uniform(5, 25)) 235 | center = int(random.uniform(2, 10)) 236 | right = int(random.uniform(5, 25)) 237 | rep = int(random.uniform(10, 100)) 238 | neuro_trigraph_stack_test(left, center, right, rep) 239 | print("Passed:" + str(i)) 240 | print("#######################\n") 241 | 242 | 243 | if __name__ == "__main__": 244 | # test case 1 245 | g1 = {1: [4], 2: [4, 5], 3: [5], 4: [6, 7], 5: [8], 6: [], 7: [], 8: []} 246 | paths = min_path_cover(g1) 247 | check_path_cover(g1, paths) 248 | assert len(paths) == 3, ("Number of paths incorrect. Correct number" + 249 | "of paths is 3, and returned number of path is " + str(len(paths))) 250 | 251 | # test case 2 252 | g2 = {1: [4], 2: [4, 5], 3: [5], 4: [6], 5: [6], 6: []} 253 | paths = min_path_cover(g2) 254 | check_path_cover(g2, paths) 255 | assert len(paths) == 3, ("Number of paths incorrect. Correct number" + 256 | "of paths is 3, and returned number of path is " + str(len(paths))) 257 | 258 | # test case 3 259 | g3 = {1: [4], 2: [5], 3: [6, 7], 4: [8], 5: [8, 9, 10], 6: [10], 7: [9], 260 | 8: [11, 12], 9: [12], 10: [12, 13], 11: [], 12: [], 13: []} 261 | paths = min_path_cover(g3) 262 | check_path_cover(g3, paths) 263 | assert len(paths) == 4, ("Number of paths incorrect. Correct number" + 264 | "of paths is 4, and returned number of path is " + str(len(paths))) 265 | 266 | # test case 4 267 | g4 = {1: [4], 2: [5], 3: [6, 7], 4: [8], 5: [8], 6: [8, 9], 7: [10], 268 | 8: [11], 9: [11], 10: [12, 13, 14], 11: [], 12: [], 13: [], 14: []} 269 | paths = min_path_cover(g4) 270 | check_path_cover(g4, paths) 271 | assert len(paths) == 6, ("Number of paths incorrect. Correct number" + 272 | "of paths is 6, and returned number of path is " + str(len(paths))) 273 | 274 | # test case 6 275 | def neuro_trigraph_test(left, center, right, p=1.0): 276 | ''' 277 | Create three layer (neuro trigraph) test cases 278 | Parameters: 279 | left (int): number of vertices on the left layer 280 | center (int): number of vertices in the middle layer 281 | right (int): number of vertices on the right layer 282 | p (float between 0 and 1): shuffle_p 283 | ''' 284 | edges1, edges2 = neur_trig_edges(left, right, center, shuffle_p=p) 285 | ans = min_cover_trigraph(edges1, edges2) 286 | g = graph_converter(edges1, edges2) 287 | paths = min_path_cover(g) 288 | check_path_cover(g, paths) 289 | assert len(paths) == len(ans), "Number of paths incorrect. Correct number of paths is " + str(len(ans)) + ",\ 290 | and returned number of path is " + str(len(paths)) 291 | 292 | for i in range(100): 293 | left = int(random.uniform(2, 100)) 294 | center = int(random.uniform(2, 100)) 295 | right = int(random.uniform(2, 100)) 296 | p = random.random() 297 | neuro_trigraph_test(left, center, right, p) 298 | tst_stack() 299 | -------------------------------------------------------------------------------- /graphing/special_graphs/neurograph/toy_graph.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class NeuralGraphVert(): 4 | def __init__(self, key, layer, layer_ix=0): 5 | self.key = key 6 | self.layer = layer 7 | self.layer_ix = layer_ix 8 | 9 | 10 | class ToyGraphs(): 11 | @staticmethod 12 | def toy_graph_1(): 13 | g = {} 14 | 15 | v4 = NeuralGraphVert(4, 2, 1) 16 | v5 = NeuralGraphVert(5, 2, 2) 17 | 18 | v6 = NeuralGraphVert(6, 3, 1) 19 | v7 = NeuralGraphVert(7, 3, 2) 20 | v8 = NeuralGraphVert(8, 3, 3) 21 | 22 | g[1] = [v4] 23 | g[2] = [v4, v5] 24 | g[3] = [v5] 25 | 26 | g[4] = [v6, v7] 27 | g[5] = [v8] 28 | 29 | g[6] = [] 30 | g[7] = [] 31 | g[8] = [] 32 | 33 | return g 34 | 35 | @staticmethod 36 | def toy_graph_2(): 37 | g = {} 38 | 39 | v2 = NeuralGraphVert(2, 2, 1) 40 | v3 = NeuralGraphVert(3, 2, 2) 41 | v4 = NeuralGraphVert(4, 2, 3) 42 | 43 | v5 = NeuralGraphVert(5, 3, 1) 44 | v6 = NeuralGraphVert(6, 3, 2) 45 | v7 = NeuralGraphVert(7, 3, 3) 46 | 47 | v8 = NeuralGraphVert(8, 4, 1) 48 | v9 = NeuralGraphVert(9, 4, 2) 49 | v10 = NeuralGraphVert(10, 4, 3) 50 | v11 = NeuralGraphVert(11, 4, 4) 51 | 52 | g[1] = [v2, v3, v4] 53 | g[2] = [v5] 54 | g[3] = [v6] 55 | g[4] = [v6, v7] 56 | 57 | g[5] = [v8] 58 | g[6] = [v9] 59 | g[7] = [v10, v11] 60 | 61 | return g 62 | -------------------------------------------------------------------------------- /graphing/toy_graphs/ukraine.py: -------------------------------------------------------------------------------- 1 | from geopy.geocoders import Nominatim 2 | import numpy as np 3 | import networkx as nx 4 | from PIL import Image, ImageDraw, ImageFont 5 | 6 | 7 | ukr_grph_ts = { 8 | 'staging-1': {'chernihiv': 5}, 9 | 'chernihiv': {'brovary': 4}, 10 | 'brovary': {'kyiv': 1}, 11 | 'boryspil': {'kyiv': 1.3, 'pereiaslav': 2.3}, 12 | 'pereiaslav': {'boryspil': 3.3, 'kremenchuk': 2.5}, 13 | 'kremenchuk': {'cherkasy': 2.3, 'pereiaslav': 3.1, 'poltava': 2.6, 'dnipro': 2.4, 'kropyvnytskyi': 2.0}, 14 | 'cherkasy': {'kyiv': 3.2, 'kremenchuk': 2.2}, 15 | 'bila_tserkva': {'kyiv': 3.1, 'uman': 2.5}, 16 | 'irpin': {'kyiv': 5.0, 'zhytomyr': 2.0}, 17 | 'kyiv': {'brovary': 1.0, 'irpin': 1.0, 'bila_tserkva': 1.8, 'cherkasy': 2.0, 'boryspil': 1.4, 'pyriatyn': 2.1}, 18 | 'uman': {'vinnytsia': 2.0, 'odesa': 2.2, 'kropyvnytskyi': 2.1, 'bila_tserkva': 2.1}, 19 | 'vinnytsia': {'zhytomyr': 1.7, 'uman': 1.5, 'odesa': 2.1}, 20 | 'odesa': {'vinnytsia': 2.4, 'uman': 2.1, 'mykolaiv': 1.8}, 21 | 'kropyvnytskyi': {'kryvyi_rih': 1.2, 'kremenchuk': 1.5, 'uman': 2.2, 'mykolaiv': 1.8}, 22 | 'dnipro': {'kryvyi_rih': 1.9, 'zaporizhzhia': 1.1, 'pavlohrad': 1.6, 'kharkiv': 2.1, 'kremenchuk': 2.3}, 23 | 'staging-2': {'sumy': 1.4}, 24 | 'sumy': {'kyiv': 4.5, 'kharkiv': 2.4}, 25 | 'staging-3': {'kharkiv': 0.8}, 26 | 'kharkiv': {'sumy': 1.9, 'okhtyrka': 1.5, 'poltava': 1.5, 'dnipro': 2.8, 'luhansk': 2.2}, 27 | 'staging-4': {'luhansk': 0.4}, 28 | 'staging-5': {'donetsk': 0.5}, 29 | 'luhansk': {'kharkiv': 1.7, 'donetsk': 0.8}, 30 | 'donetsk': {'mariupol': 1.4, 'zaporizhzhia': 1.8, 'pokrovsk': 1.1, 'luhansk': 2.0}, 31 | 'mariupol': {'berdyansk': 0.4, 'donetsk': 0.7}, 32 | 'berdyansk': {'mariupol': 0.4, 'melitopol': 0.5, 'zaporizhzhia': 1.4}, 33 | 'staging-6': {'melitopol': 0.1}, 34 | 'staging-7': {'kherson': 0.3}, 35 | 'kherson': {'mykolaiv': 1.0, 'melitopol': 1.3}, 36 | 'mykolaiv': {'odesa': 1.3, 'kherson': 1.1, 'kryvyi_rih': 0.9, 'kropyvnytskyi': 0.85}, 37 | 'poltava': {'lubny': 2, 'kremenchuk': 2.5, 'kharkiv': 2.2}, 38 | 'lubny': {'pyriatyn': 1.4, 'poltava': 1.8}, 39 | 'zhytomyr': {'vinnytsia': 2.1, 'irpin': 1.6}, 40 | 'pyriatyn': {'okhtyrka': 1.7, 'kyiv': 2.8, 'lubny': 0.9}, 41 | 'okhtyrka': {'pyriatyn': 2.1, 'kharkiv': 2.2}, 42 | 'kryvyi_rih': {'mykolaiv': 1.8, 'kropyvnytskyi': 0.9, 'dnipro': 2.4}, 43 | 'zaporizhzhia': {'dnipro': 0.8, 'donetsk': 2.1, 'berdyansk': 1.8, 'melitopol': 1.5}, 44 | 'pavlohrad': {'dnipro': 2.1, 'pokrovsk': 0.8}, 45 | 'pokrovsk': {'pavlohrad': 0.8, 'donetsk': 0.7}, 46 | 'melitopol': {'berdyansk': 0.7, 'zaporizhzhia': 1.1, 'kherson': 2.3} 47 | } 48 | 49 | geolocator = Nominatim(user_agent="MyApp") 50 | 51 | coord = { 52 | 'staging-1': (362, 47), 53 | 'staging-2': (908, 140), 54 | 'staging-3': (1130, 260), 55 | 'staging-4': (1374, 672), 56 | 'staging-5': (1260, 824), 57 | 'staging-6': (828, 993), 58 | 'staging-7': (692, 1013), 59 | 'chernihiv': (383, 172), 60 | 'brovary': (295, 273), 61 | 'kyiv': (258,315), 62 | 'irpin': (218, 314), 63 | 'zhytomyr': (110,346), 64 | 'vinnytsia': (105,542), 65 | 'odesa': (262,869), 66 | 'mykolaiv': (494,781), 67 | 'kherson': (623,878), 68 | 'kryvyi_rih': (666,733), 69 | 'kropyvnytskyi': (583,662), 70 | 'kremenchuk': (693, 535), 71 | 'pereiaslav': (508,444), 72 | 'cherkasy': (442,479), 73 | 'boryspil': (346,364), 74 | 'pokrovsk': (1070, 692), 75 | 'uman': (260,654), 76 | 'luhansk': (1265,661), 77 | 'donetsk': (1076,773), 78 | 'mariupol': (1032,868), 79 | 'berdyansk': (949,865), 80 | 'sumy': (817,252), 81 | 'kharkiv': (1049,382), 82 | 'poltava': (827,415), 83 | 'melitopol': (837,862), 84 | 'zaporizhzhia': (838,718), 85 | 'dnipro': (838, 614), 86 | 'pavlohrad': (996,629), 87 | 'kremanchuk': (694,536), 88 | 'pyriatyn': (560,329), 89 | 'lubny': (666,369), 90 | 'bila_tserkva': (250,479), 91 | 'okhtyrka': (815,348) 92 | } 93 | 94 | 95 | posns = {} 96 | x = [] 97 | y1 = [] 98 | y2 = [] 99 | for k in ukr_grph_ts.keys(): 100 | if 'staging' not in k: 101 | location = geolocator.geocode(k) 102 | lati, longi = location.latitude, location.longitude 103 | posns[k] = (lati, longi) 104 | if k in coord: 105 | x.append([1, lati, longi]) 106 | y1.append(coord[k][0]) 107 | y2.append(coord[k][1]) 108 | 109 | x = np.array(x) 110 | y1 = np.array(y1) 111 | y2 = np.array(y2) 112 | 113 | beta1 = np.linalg.solve(np.dot(x.T,x),np.dot(x.T,y1)) 114 | beta2 = np.linalg.solve(np.dot(x.T,x),np.dot(x.T,y2)) 115 | 116 | for k in ukr_grph_ts.keys(): 117 | if k not in coord: 118 | lati, longi = posns[k] 119 | x_ht = np.array([1, lati, longi]) 120 | y1 = np.dot(beta1, x_ht) 121 | y2 = np.dot(beta2, x_ht) 122 | coord[k] = (y1, y2) 123 | 124 | 125 | im = Image.new("RGB", (1500, 1500), (0, 0, 0)) 126 | draw = ImageDraw.Draw(im, "RGBA") 127 | 128 | for k in coord.keys(): 129 | pt = coord[k] 130 | draw.ellipse( 131 | (pt[0] - 8, pt[1] - 8, pt[0] + 8, pt[1] + 8), 132 | fill=(255, 0, 0, 150), 133 | outline=(0, 0, 0), 134 | ) 135 | font = ImageFont.truetype("Arial.ttf", 20) 136 | draw.text((pt[0] + 2, pt[1] + 2), k, "orange", font=font) 137 | 138 | for k in ukr_grph_ts.keys(): 139 | for kk in ukr_grph_ts[k].keys(): 140 | pt1 = coord[k] 141 | pt2 = coord[kk] 142 | draw.line((pt1[0], pt1[1], pt2[0], pt2[1]), (102, 255, 51, 120), width=2) 143 | 144 | 145 | G = nx.DiGraph() 146 | 147 | for k in ukr_grph_ts.keys(): 148 | for kk in ukr_grph_ts[k].keys(): 149 | G.add_edge(k, kk, weight=int(100*ukr_grph_ts[k][kk]), capacity=720) 150 | 151 | G.add_edge("s", 'staging-1', weight=0, capacity=np.inf) 152 | G.add_edge("s", 'staging-2', weight=0, capacity=np.inf) 153 | G.add_edge("s", 'staging-3', weight=0, capacity=np.inf) 154 | G.add_edge("s", 'staging-4', weight=0, capacity=np.inf) 155 | G.add_edge("s", 'staging-5', weight=0, capacity=np.inf) 156 | G.add_edge("s", 'staging-6', weight=0, capacity=np.inf) 157 | G.add_edge("s", 'staging-7', weight=0, capacity=np.inf) 158 | 159 | G.add_node("s", demand=-5000) 160 | G.add_node("kyiv", demand=3000) 161 | G.add_node("kharkiv", demand=1000) 162 | G.add_node("mariupol", demand=500) 163 | G.add_node("odesa", demand=500) 164 | 165 | flowDict = nx.min_cost_flow(G) 166 | 167 | for k in flowDict.keys(): 168 | if k != 's': 169 | for kk in flowDict[k].keys(): 170 | if flowDict[k][kk]>0: 171 | pt1 = coord[k] 172 | pt2 = coord[kk] 173 | draw.line((pt1[0], pt1[1], pt2[0], pt2[1]), (255, 10, 51, 80), width=int(30/720*flowDict[k][kk])) 174 | 175 | im.show() 176 | -------------------------------------------------------------------------------- /graphing/traversal/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /graphing/traversal/clr_traversal.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import queue 3 | from collections import defaultdict 4 | 5 | 6 | class Node(): 7 | def __init__(self, val, nxt=None, color="white", 8 | pi=None, d=np.inf, f=np.inf, key=None): 9 | self.nxt = nxt 10 | self.val = val 11 | self.color = color 12 | self.pi = pi 13 | self.d = d 14 | self.f = f 15 | if key is None: 16 | self.key = val 17 | else: 18 | self.key = key 19 | 20 | 21 | class Graph1(): 22 | def __init__(self, edges, excl_verts={}): 23 | self.white_verts = set() 24 | self.grey_verts = set() 25 | self.black_verts = set() 26 | self.adj = defaultdict(dict) 27 | # We'll need the reverse graph as well. 28 | self.vert_props = {} 29 | self.edges = edges 30 | self.time = 0 31 | for ed in edges: 32 | vert_0 = ed[0] 33 | vert_1 = ed[1] 34 | if vert_0 not in excl_verts and vert_1 not in excl_verts: 35 | self.white_verts.add(vert_0) 36 | self.white_verts.add(vert_1) 37 | # Save graph as an adjacency list. 38 | self.adj[vert_0][vert_1] = 0 39 | self.vert_props[vert_0] = Node(vert_0) 40 | self.vert_props[vert_1] = Node(vert_1) 41 | 42 | def print_vert_props(self): 43 | for k in self.vert_props.keys(): 44 | print(str(self.vert_props[k].__dict__)) 45 | 46 | def bfs(self, s): 47 | self.grey_verts.add(s) 48 | self.vert_props[s].d = 0 49 | q = queue.Queue() 50 | q.put(s) 51 | while q.qsize() > 0: 52 | u = q.get() 53 | for v in self.adj[u]: 54 | if v in self.white_verts and v not in self.grey_verts\ 55 | and v not in self.black_verts: 56 | self.grey_verts.add(v) 57 | self.vert_props[v].d = self.vert_props[u].d + 1 58 | self.vert_props[v].pi = u 59 | q.put(v) 60 | self.black_verts.add(u) 61 | 62 | def dfs(self): 63 | for u in self.vert_props.keys(): 64 | if self.vert_props[u].color == "white": 65 | self.dfs_visit(u) 66 | 67 | def dfs_visit(self, u): 68 | self.time += 1 69 | self.vert_props[u].d = self.time 70 | self.vert_props[u].color = "grey" 71 | for v in self.adj[u]: 72 | if self.vert_props[v].color == "white": 73 | self.vert_props[v].pi = u 74 | self.dfs_visit(v) 75 | self.vert_props[u].color = "black" 76 | self.time += 1 77 | self.vert_props[u].f = self.time 78 | 79 | 80 | def tst2(): 81 | edges = [['s1', 'a'], 82 | ['s1', 'd'], 83 | ['a', 'b'], 84 | ['d', 'b'], 85 | ['b', 'c'], 86 | ['d', 'e'], 87 | ['e', 'c'], 88 | ['c', 'd1'], 89 | ['s2', 'd'], 90 | ['d', 'e'], 91 | ['e', 'c'], 92 | ['e', 'f'], 93 | ['f', 'd2'], 94 | ['s3', 'g'], 95 | ['g', 'e'], 96 | ['e', 'f'], 97 | ['f', 'd2']] 98 | g1 = Graph1(edges) 99 | g1.bfs('s1') 100 | g1.print_vert_props() 101 | g2 = Graph1(edges) 102 | g2.dfs() 103 | g2.print_vert_props() 104 | -------------------------------------------------------------------------------- /graphing/traversal/walks.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import cmath 3 | import matplotlib.pyplot as plt 4 | 5 | 6 | def polygon_paths(n=3,s=1,h=5): 7 | omeg = cmath.exp((2 * cmath.pi * 1j) / n) 8 | sum1 = 0 9 | for j in range(0,n): 10 | sum1+=omeg**(-j*(h+s))*(1+omeg**(2*j))**h 11 | return int(sum1.real/n) 12 | 13 | 14 | def tst(): 15 | paths5 = [polygon_paths(n=5,h=h,s=2) for h in range(1,8)] 16 | paths6 = [polygon_paths(n=6,h=h,s=2) for h in range(1,8)] 17 | paths7 = [polygon_paths(n=7,h=h,s=2) for h in range(1,8)] 18 | paths8 = [polygon_paths(n=8,h=h,s=2) for h in range(1,8)] 19 | 20 | plt.plot(np.arange(1,8),paths5,label="5") 21 | plt.plot(np.arange(1,8),paths6,label="6") 22 | plt.plot(np.arange(1,8),paths7,label="7") 23 | plt.plot(np.arange(1,8),paths8,label="8") 24 | 25 | plt.legend() 26 | plt.show() 27 | 28 | 29 | -------------------------------------------------------------------------------- /graphing/use_cases/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /graphing/use_cases/find_faulty_network_link.py: -------------------------------------------------------------------------------- 1 | import queue 2 | from collections import defaultdict 3 | 4 | 5 | class Graph(): 6 | def __init__(self, edges, excl_verts={}): 7 | self.white_verts = set() 8 | self.grey_verts = set() 9 | self.black_verts = set() 10 | self.adj = defaultdict(dict) 11 | # We'll need the reverse graph as well. 12 | self.adj_rev = defaultdict(dict) 13 | self.vert_wts = {} 14 | self.edges = edges 15 | for ed in edges: 16 | vert_0 = ed[0] 17 | vert_1 = ed[1] 18 | if vert_0 not in excl_verts and vert_1 not in excl_verts: 19 | self.white_verts.add(vert_0) 20 | self.white_verts.add(vert_1) 21 | # Save graph as an adjacency list. 22 | self.adj[vert_0][vert_1] = 0 23 | # We need both the regular graph and reversed graph. 24 | self.adj_rev[vert_1][vert_0] = 0 25 | self.vert_wts[vert_0] = 0 26 | self.vert_wts[vert_1] = 0 27 | 28 | def bfs_probs(self, s, rev=False): 29 | self.vert_wts[s] = 1 30 | self.grey_verts.add(s) 31 | q = queue.Queue() 32 | q.put(s) 33 | if not rev: 34 | e_lst = self.adj 35 | else: 36 | e_lst = self.adj_rev 37 | while q.qsize() > 0: 38 | u = q.get() 39 | for v in e_lst[u]: 40 | if v in self.white_verts and v not in self.grey_verts\ 41 | and v not in self.black_verts: 42 | self.grey_verts.add(v) 43 | q.put(v) 44 | e_lst[u][v] += self.vert_wts[u]/len(e_lst[u]) 45 | self.vert_wts[v] += self.vert_wts[u]/len(e_lst[u]) 46 | self.vert_wts[u] = 0 47 | self.black_verts.add(u) 48 | 49 | 50 | def reachable_subgraph(edges, source='s1', dest='d1'): 51 | g3 = Graph(edges) 52 | # We don't care about the probabilities, just about BFS visits 53 | g3.bfs_probs(source) 54 | g4 = Graph(edges) 55 | # We don't care about the probabilities, just about BFS visits 56 | g4.bfs_probs(dest, rev=True) 57 | excl_verts = g3.vert_wts.keys() - \ 58 | g4.black_verts.intersection(g3.black_verts) 59 | g5 = Graph(edges, excl_verts) 60 | return g5 61 | 62 | 63 | def tst(): 64 | # This kind of graph is not split neatly into layers. 65 | # So, one iteration of bfs is not enough. 66 | edges = [['s', 'a'], 67 | ['a', 'c'], 68 | ['s', 'e'], 69 | ['s', 'b'], 70 | ['b', 'd'], 71 | ['e', 'd'], 72 | ['c', 'd'], 73 | ['a', 'e']] 74 | g1 = Graph(edges) 75 | g1.bfs_probs('s') 76 | # This kind of graph is split neatly into layers 77 | # like a networking topology graph would. So, 78 | # one iteration of the bfs is enough. 79 | edges = [['s1', 'a'], 80 | ['s1', 'd'], 81 | ['a', 'b'], 82 | ['d', 'b'], 83 | ['b', 'c'], 84 | ['d', 'e'], 85 | ['e', 'c'], 86 | ['c', 'd1']] 87 | g2 = Graph(edges) 88 | g2.bfs_probs('s1') 89 | print("Here is the graph with probabilities of the error populated.") 90 | print(g2.adj) 91 | # Now, how to get rid of edges irrelevant to the network topology. 92 | edges = [['s1', 'a'], 93 | ['s1', 'd'], 94 | ['a', 'b'], 95 | ['d', 'b'], 96 | ['b', 'c'], 97 | ['d', 'e'], 98 | ['e', 'c'], 99 | ['c', 'd1'], 100 | ['s2', 'd'], 101 | ['d', 'e'], 102 | ['e', 'c'], 103 | ['e', 'f'], 104 | ['f', 'd2'], 105 | ['s3', 'g'], 106 | ['g', 'e'], 107 | ['e', 'f'], 108 | ['f', 'd2']] 109 | g3 = reachable_subgraph(edges, 's1', 'd1') 110 | g3.bfs_probs('s1') 111 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup(name='graphing', 4 | version='0.0.8', 5 | url='https://github.com/microsoft/graphing', 6 | license='MIT', 7 | author='Rohit Pandey', 8 | author_email='rohitpandey576@gmail.com' 9 | description='Add static script_dir() method to Path', 10 | packages=find_packages(exclude=['tests']), 11 | long_description=open('README.md').read(), 12 | zip_safe=False) 13 | --------------------------------------------------------------------------------