├── .codecov.yml ├── .coveragerc ├── .gitignore ├── .travis.yml ├── LICENCE ├── README.rst ├── multinet ├── __init__.py ├── bipartite.py ├── builder.py ├── classes.py ├── information_measure.py ├── l_core.py ├── random_models.py ├── shortest_path.py ├── tests │ ├── test_bipartite.py │ ├── test_classes.py │ ├── test_information_measure.py │ ├── test_random_models.py │ └── test_shortest_path.py └── util │ ├── __init__.py │ ├── filter_helper.py │ ├── layer_helper.py │ └── weight_helper.py ├── setup.py └── tools └── travis ├── linux_install.sh └── script.sh /.codecov.yml: -------------------------------------------------------------------------------- 1 | comment: off -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | # .coveragerc to control coverage.py 2 | [run] 3 | omit = */tests/* 4 | include = multinet/* 5 | 6 | [paths] 7 | source = 8 | multinet/ 9 | */multinet/ 10 | 11 | -------------------------------------------------------------------------------- /.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 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # Jupyter Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # SageMath parsed files 81 | *.sage.py 82 | 83 | # dotenv 84 | .env 85 | 86 | # virtualenv 87 | .venv 88 | venv/ 89 | ENV/ 90 | 91 | # Spyder project settings 92 | .spyderproject 93 | .spyproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | 98 | # mkdocs documentation 99 | /site 100 | 101 | # mypy 102 | .mypy_cache/ 103 | 104 | # vscode 105 | .vscode/ 106 | 107 | # customized 108 | *~ 109 | pic/* 110 | result/* 111 | writeup/* 112 | src/.ropeproject/* 113 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | matrix: 4 | include: 5 | - os: linux 6 | python: 2.7 7 | env: 8 | - REPORT_COVERAGE=1 9 | - python: 2.7 10 | - python: 3.6 11 | 12 | before_install: 13 | - uname -a 14 | - printenv 15 | 16 | install: 17 | - source tools/travis/linux_install.sh 18 | 19 | script: 20 | - source tools/travis/script.sh 21 | 22 | after_success: 23 | - if [[ "${REPORT_COVERAGE}" == 1 ]]; then 24 | codecov; 25 | fi 26 | 27 | notification: 28 | email:false -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Haochen Wu 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.rst: -------------------------------------------------------------------------------- 1 | multinet 2 | ======== 3 | 4 | |build| |codecov| 5 | 6 | multinet is a networkx extension to handle multiplex network. It contains some useful function to handle operations that specific to multiplex network and it's still pretty much compatible with networkx functions as the aggregated network. 7 | 8 | It is currently still in progress. 9 | 10 | Install 11 | ------- 12 | Install from the source:: 13 | 14 | $ pip install . 15 | 16 | Simple Example 17 | -------------- 18 | A two layer multiplex network:: 19 | 20 | >>> import multinet as mn 21 | >>> mg = mn.Multinet() 22 | >>> mg.add_node(1) 23 | >>> mg.add_node(2) 24 | >>> mg.add_edge(1, 2, 'Layer_1') 25 | >>> mg.add_edge(1, 2, 'Layer_2') 26 | >>> mg.number_of_layers() 27 | 2 28 | 29 | .. |build| image:: https://travis-ci.org/wuhaochen/multinet.svg?branch=master 30 | :target: https://travis-ci.org/wuhaochen/multinet 31 | :alt: Continuous Integration Status 32 | 33 | .. |codecov| image:: https://codecov.io/gh/wuhaochen/multinet/branch/master/graph/badge.svg 34 | :target: https://codecov.io/gh/wuhaochen/multinet 35 | :alt: Code Coverage Status 36 | -------------------------------------------------------------------------------- /multinet/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from multinet.classes import * 4 | from multinet.builder import * 5 | from multinet.bipartite import * 6 | from multinet.random_models import * 7 | from multinet.shortest_path import * 8 | from multinet.l_core import * 9 | from multinet.information_measure import * 10 | -------------------------------------------------------------------------------- /multinet/bipartite.py: -------------------------------------------------------------------------------- 1 | """Project a multiplex network to a bipartite graph. 2 | """ 3 | from __future__ import division 4 | 5 | import networkx as nx 6 | import multinet as mn 7 | 8 | try: 9 | basestring 10 | except NameError: 11 | basestring = str 12 | 13 | # These following lines provide utilities to add and remove prefix to the layer 14 | # node. In case of a node might have same name as a layer. 15 | _layer_prefix = 'Layer_' 16 | 17 | _prefix = lambda x: _layer_prefix + str(x) 18 | _remove_prefix = lambda x: x[len(_layer_prefix):] 19 | 20 | 21 | def _add_layer_nodes(bg, layers): 22 | """Add nodes represents the layers to the bipartite graph. 23 | 24 | Parameters: 25 | ----------- 26 | bg: nx.Graph 27 | Bipartite graph to operate on. 28 | 29 | layers: list 30 | A list of layers. 31 | 32 | """ 33 | layer_names = map(_prefix,layers) 34 | bg.add_nodes_from(layer_names, bipartite=0) 35 | 36 | 37 | def bipartize_by_node(mg, weighted=True): 38 | """Project a Multinet to a bipartite graph using layer-node view. 39 | 40 | Parameters: 41 | ----------- 42 | mg: Multinet 43 | Mulitplex network to project. 44 | 45 | weighted: bool 46 | Whether or not use the weight information in the multiplex network. 47 | 48 | """ 49 | bipartite_graph = nx.Graph() 50 | _add_layer_nodes(bipartite_graph,mg.layers()) 51 | 52 | bipartite_graph.add_nodes_from(mg,bipartite = 1) 53 | for layer in mg.layers(): 54 | sg = mg.sub_layer(layer,remove_isolates=True) 55 | for node in sg.nodes(): 56 | if weighted: 57 | w = sg.degree(node,weight='weight') 58 | else: 59 | w = sg.degree(node) 60 | if w > 0: 61 | bipartite_graph.add_edge(_prefix(layer),node,weight=w) 62 | 63 | return bipartite_graph 64 | 65 | 66 | def bipartize_by_edge(mg, weighted=True): 67 | """Project a Multinet to a bipartite graph using layer-edge view. 68 | 69 | Parameters: 70 | ----------- 71 | mg: Multinet 72 | Mulitplex network to project. 73 | 74 | weighted: bool 75 | Whether or not use the weight information in the multiplex network. 76 | 77 | """ 78 | bipartite_graph = nx.Graph() 79 | _add_layer_nodes(bipartite_graph,mg.layers()) 80 | 81 | bipartite_graph.add_nodes_from(mg.edges(),bipartite = 1) 82 | for u,v in mg.edges(): 83 | layers = mg[u][v]['multiplex'] 84 | for layer in layers: 85 | if weighted: 86 | w = layers[layer] 87 | else: 88 | w = 1.0 89 | bipartite_graph.add_edge(_prefix(layer),(u,v),weight=w) 90 | 91 | return bipartite_graph 92 | 93 | 94 | def bipartize(mg,mode,weighted=True): 95 | """Project a Multinet to a bipartite graph using layer-edge view. 96 | 97 | Parameters: 98 | ----------- 99 | mg: Multinet 100 | Mulitplex network to project. 101 | 102 | mode: str 103 | Whether using layer-node view or layer-edge view. 104 | 105 | weighted: bool 106 | Whether or not use the weight information in the multiplex network. 107 | 108 | """ 109 | if isinstance(mode,basestring): 110 | mstr = mode.lower() 111 | if mstr in set(['nodes','node','vertices','vertex','n','v']): 112 | return bipartize_by_node(mg,weighted) 113 | elif mstr in set(['edges','edge','arcs','arc','e','a']): 114 | return bipartize_by_edge(mg,weighted) 115 | else: 116 | raise Exception("Mode does not exist!") 117 | else: 118 | raise Exception("Mode must be string!") 119 | 120 | 121 | def bipartite_sets(bg): 122 | """Return two nodes sets of a bipartite graph. 123 | 124 | Parameters: 125 | ----------- 126 | bg: nx.Graph 127 | Bipartite graph to operate on. 128 | 129 | """ 130 | top = set(n for n, d in bg.nodes(data=True) if d['bipartite']==0) 131 | bottom = set(bg) - top 132 | return (top, bottom) 133 | 134 | 135 | def reconstruct_from_bipartite(bg, create_using=mn.DiMultinet()): 136 | """Reconstruct a multiplex network from a layer-edge bipartite graph. 137 | 138 | Parameters: 139 | ----------- 140 | bg: nx.Graph 141 | A layer-edge bipartite graph that used to reconstruct the multiplex 142 | network. 143 | 144 | """ 145 | top, bottom = bipartite_sets(bg) # pylint: disable=unused-variable 146 | mg = create_using 147 | 148 | for nodes in bottom: 149 | mg.add_nodes_from(nodes) 150 | u = nodes[0] 151 | v = nodes[1] 152 | for layer in bg[nodes]: 153 | layer = _remove_prefix(layer) 154 | mg.add_edge(u,v,layer) 155 | 156 | return mg 157 | 158 | 159 | def attach_importance(bg): 160 | """New measure working in progress. 161 | """ 162 | for node in bg.nodes(): 163 | bg.node[node]['imp'] = 0.0 164 | 165 | import itertools 166 | for u,v in itertools.combinations(bg.nodes(),2): 167 | common = set(bg[u]) & set(bg[v]) 168 | for node in common: 169 | bg.node[node]['imp'] += 1/len(common) 170 | 171 | for node in bg.nodes(): 172 | bg.node[node]['nimp'] = bg.node[node]['imp']/bg.degree(node) 173 | -------------------------------------------------------------------------------- /multinet/builder.py: -------------------------------------------------------------------------------- 1 | """Build Multinet instance from csv file. 2 | 3 | """ 4 | import networkx as nx 5 | import multinet as mn 6 | import csv 7 | 8 | import multinet.util as util 9 | 10 | def multinet_from_csv( 11 | file_name, 12 | filter_func=util.default_filter, 13 | weight_func=util.default_weight, 14 | layer_func=util.default_layer, 15 | ow='ORIGIN', dw='DEST', 16 | create_using=None, 17 | **csv_reader_argv): 18 | """Build Multinet from csv files. 19 | 20 | Parameters: 21 | ----------- 22 | filename: str 23 | The path to csv file. 24 | 25 | filter_func: func 26 | A function takes line description and a line from the csv file and 27 | return a bool. See util/filter_helper for detail. 28 | 29 | weight_func: func or str 30 | A function takes line description and a line from the csv file and 31 | return a number. See util/weight_helper for detail. 32 | 33 | layer_func: func or str 34 | A function takes line description and a line from the csv file and 35 | return a str. See util/layer_helper for detail. 36 | 37 | ow, dw: str 38 | The key to get nodes name. 39 | 40 | csv_reader_argv: 41 | Arguments for internal csv reader. If not specified, use default 42 | settings. See Python csv module for detail. 43 | """ 44 | 45 | index_dict = {} 46 | if create_using is None: 47 | create_using = mn.DiMultinet() 48 | mg = create_using 49 | 50 | if type(weight_func) == str: 51 | weight_func = util.weight_from_string(weight_func) 52 | 53 | if type(layer_func) == str: 54 | layer_func = util.layer_from_string(layer_func) 55 | 56 | with open(file_name) as netfile: 57 | if not csv_reader_argv: 58 | netreader = csv.reader(netfile, 59 | delimiter=',', 60 | quotechar='\"', 61 | quoting=csv.QUOTE_NONNUMERIC) 62 | else: 63 | netreader = csv.reader(netfile,**csv_reader_argv) 64 | 65 | index_line = next(netreader) 66 | 67 | index = 0 68 | for item in index_line: 69 | index_dict[item] = index 70 | index += 1 71 | 72 | origin_index = index_dict[ow] 73 | dest_index = index_dict[dw] 74 | 75 | for line in netreader: 76 | if not filter_func(index_dict,line): 77 | continue 78 | 79 | origin = str(line[origin_index]) 80 | dest = str(line[dest_index]) 81 | 82 | layer = layer_func(index_dict,line) 83 | weight = weight_func(index_dict,line) 84 | 85 | mg.add_node(origin) 86 | mg.add_node(dest) 87 | 88 | mg.aggregate_edge(origin,dest,layer,weight) 89 | 90 | return mg 91 | -------------------------------------------------------------------------------- /multinet/classes.py: -------------------------------------------------------------------------------- 1 | """Multiplex network class. 2 | 3 | """ 4 | 5 | import networkx as nx 6 | 7 | 8 | class Multinet(nx.Graph): 9 | """ 10 | Undirected Multiplex network class. 11 | """ 12 | 13 | def __init__(self, container_id='multiplex'): 14 | """Initialize a Multinet with empty layer list. 15 | 16 | """ 17 | nx.Graph.__init__(self) 18 | self._cid = container_id 19 | self.nx_base = nx.Graph 20 | self.graph['layers'] = [] 21 | 22 | 23 | @property 24 | def cid(self): 25 | return self._cid 26 | 27 | 28 | def _add_layer(self,layer): 29 | """Add one layer to the layer list. 30 | 31 | Parameters: 32 | ----------- 33 | layer: str 34 | layer to be added to the layer list. 35 | 36 | """ 37 | if layer not in self.layers(): 38 | self.graph['layers'].append(layer) 39 | 40 | 41 | def _remove_layer(self,layer): 42 | """Remove one layer from the layer list. 43 | 44 | Parameters: 45 | ----------- 46 | layer: str 47 | layer to be removed from the layer list. 48 | 49 | """ 50 | self.graph['layers'].remove(layer) 51 | 52 | 53 | def layers(self): 54 | """Return all the layers in the Multinet. 55 | 56 | """ 57 | return self.graph['layers'] 58 | 59 | 60 | def number_of_layers(self): 61 | """Return number of layers in the Multinet. 62 | 63 | """ 64 | return len(self.graph['layers']) 65 | 66 | 67 | def number_of_edgelets(self): 68 | """Return number of layers in the Multinet. 69 | 70 | """ 71 | return sum([len(self[u][v][self.cid]) for u, v in self.edges()]) 72 | 73 | 74 | def _init_edge(self, u, v, layer): 75 | """Initialize one edge in the Multinet for one layer. 76 | Used by add_edge() and aggregate_edge(). 77 | 78 | Parameters: 79 | ----------- 80 | u, v: 81 | Nodes the edge connects. 82 | 83 | layer: 84 | Layer the edge sit on. 85 | 86 | """ 87 | self.nx_base.add_edge(self, u, v) 88 | 89 | self._add_layer(layer) 90 | 91 | if self.cid not in self[u][v]: 92 | self[u][v][self.cid] = {} 93 | if layer not in self[u][v][self.cid]: 94 | self[u][v][self.cid][layer] = 0.0 95 | 96 | 97 | def add_edge(self, u, v, layer, weight=1.0): 98 | """Add an edge to the Multinet. 99 | 100 | If the edge already exist, set the weight to the new one. 101 | 102 | Parameters: 103 | ----------- 104 | u, v: 105 | Nodes the edge connects. 106 | 107 | layer: 108 | Layer the edge sit on. 109 | 110 | weight: float (default 1.0) 111 | The weight of the edge. 112 | 113 | """ 114 | self._init_edge(u, v, layer) 115 | self[u][v][self.cid][layer] = weight 116 | 117 | 118 | def aggregate_edge(self, u, v, layer, weight): 119 | """Aggregate an edge to the Multinet. 120 | 121 | If the edge already exist, add the weight to the existing one. 122 | 123 | Parameters: 124 | ----------- 125 | u, v: 126 | Nodes the edge connects. 127 | 128 | layer: 129 | Layer the edge sit on. 130 | 131 | weight: float (default 1.0) 132 | The weight of the edge. 133 | 134 | """ 135 | 136 | self._init_edge(u, v, layer) 137 | self[u][v][self.cid][layer] += weight 138 | 139 | 140 | def enum_edgelets(self): 141 | """Enumerate all edges in all layers. 142 | 143 | Yields: 144 | -------- 145 | Enumerate all edgelets in the form of a tuple of (u, v, layer). 146 | """ 147 | for u, v in self.edges(): 148 | edge = self[u][v][self.cid] 149 | for layer in edge: 150 | yield (u, v, layer) 151 | 152 | 153 | @property 154 | def edgelets(self): 155 | """Return a list of all the edgelets in the Multinet. 156 | 157 | Returns: 158 | A list of tuple (u, v, layer) 159 | """ 160 | return list(self.enum_edgelets()) 161 | 162 | 163 | def remove_edgelet(self, u, v, layer): 164 | """Remove a specific edge in a specific layer. 165 | 166 | Parameters: 167 | u, v: networkx node 168 | The end nodes of the edgelet to be removed. 169 | 170 | layer: str 171 | The layer of the edgelet to be removed. 172 | """ 173 | try: 174 | multi_edge = self[u][v][self.cid] 175 | multi_edge.pop(layer) 176 | if len(multi_edge) == 0: 177 | self.nx_base.remove_edge(self, u, v) 178 | except KeyError: 179 | # No edge to remove. Do nothing. 180 | pass 181 | 182 | 183 | def sub_layers(self, layers, remove_isolates=False): 184 | """Return a new Multinet instance that only contains layers specified. 185 | 186 | Parameters: 187 | ----------- 188 | layers: container 189 | The list of layers that is to be remained. 190 | The layers that is not in the original multiplex network will be 191 | ignored. 192 | 193 | remove_isolates: bool (default False) 194 | Remove the isolated nodes in the new multiplex network. 195 | 196 | """ 197 | layers = set(layers) & set(self.layers()) 198 | 199 | import copy 200 | g = copy.deepcopy(self) 201 | to_remove = [] 202 | for u,v in g.edges(): 203 | new_weight = {} 204 | for layer in layers: 205 | if layer in g[u][v][self.cid]: 206 | new_weight[layer] = g[u][v][self.cid][layer] 207 | if len(new_weight) == 0: 208 | to_remove.append((u, v)) 209 | else: 210 | g[u][v][self.cid] = new_weight 211 | g.remove_edges_from(to_remove) 212 | g.graph['layers'] = list(layers) 213 | 214 | if remove_isolates: 215 | g.remove_nodes_from(list(nx.isolates(g))) 216 | 217 | return g 218 | 219 | 220 | def sub_layer(self, layer, remove_isolates=False): 221 | """Return a new DiGraph instance that only contains one layer. 222 | 223 | Parameters: 224 | ----------- 225 | layer: str 226 | The layer intended to be extract. 227 | 228 | remove_isolates: bool (default False) 229 | Remove the isolated nodes in the new graph. 230 | 231 | """ 232 | if layer not in self.layers(): 233 | raise Exception('layer does not exist.') 234 | 235 | g = self.nx_base() 236 | g.add_nodes_from(self) 237 | for u,v in self.edges(): 238 | edge = self[u][v] 239 | if layer in edge[self.cid]: 240 | weight = edge[self.cid][layer] 241 | g.add_edge(u,v,weight=weight) 242 | if remove_isolates: 243 | g.remove_nodes_from(list(nx.isolates(g))) 244 | return g 245 | 246 | 247 | def aggregated(self): 248 | """Return a new DiGraph instance that represents the aggregated network. 249 | """ 250 | g = self.nx_base() 251 | g.add_nodes_from(self) 252 | for u,v in self.edges(): 253 | g.add_edge(u,v) 254 | g[u][v]['weight'] = sum(self[u][v][self.cid].values()) 255 | g[u][v]['nlayer'] = len(self[u][v][self.cid]) 256 | return g 257 | 258 | 259 | def merge_layers(self, layers, new_name=None): 260 | """Merge layers together with new name. 261 | 262 | Parameters: 263 | ----------- 264 | layers: 265 | The layers to be merged. 266 | 267 | new_name: str or None 268 | The name of the merged layer. If remains None, the new name will be 269 | the merged layers joined with underline. 270 | 271 | """ 272 | if not new_name: 273 | new_name = '_'.join(layers) 274 | 275 | for layer in layers: 276 | self._remove_layer(layer) 277 | 278 | self._add_layer(new_name) 279 | 280 | for u,v in self.edges(): 281 | new_weight = 0.0 282 | for layer in layers: 283 | if layer in self[u][v][self.cid]: 284 | new_weight += self[u][v][self.cid].pop(layer) 285 | if new_weight != 0: 286 | self[u][v][self.cid][new_name] = new_weight 287 | 288 | 289 | def add_layer(self, layer_graph, layer_name): 290 | """Add a new layer from a DiGraph. 291 | The existing edge will be replaced by the new one. 292 | 293 | Parameters: 294 | ----------- 295 | layer_graph: nx.DiGraph 296 | The layers to be added. 297 | 298 | layer_name: str 299 | The name of the new layer. 300 | 301 | """ 302 | self.add_nodes_from(layer_graph) 303 | self._add_layer(layer_name) 304 | 305 | for u, v in layer_graph.edges(): 306 | if 'weight' in layer_graph[u][v]: 307 | weight = layer_graph[u][v]['weight'] 308 | else: 309 | weight = 1.0 310 | self.add_edge(u,v,layer_name,weight) 311 | 312 | 313 | def empty_layers(self): 314 | """Return a list of all empty layers. 315 | 316 | """ 317 | layers = set(self.layers()) 318 | nonempty = set() 319 | 320 | for u,v in self.edges(): 321 | for layer in self[u][v][self.cid]: 322 | nonempty.add(layer) 323 | 324 | return list(layers-nonempty) 325 | 326 | 327 | def remove_empty_layers(self): 328 | """Remove all empty layers. 329 | 330 | """ 331 | to_remove = self.empty_layers() 332 | for layer in to_remove: 333 | self._remove_layer(layer) 334 | 335 | 336 | def remove_layer(self, layer): 337 | """Remove one specific layer. 338 | 339 | """ 340 | edges_to_remove = list() 341 | 342 | for u,v in self.edges(): 343 | if layer in self[u][v][self.cid]: 344 | self[u][v][self.cid].pop(layer) 345 | if len(self[u][v][self.cid]) == 0: 346 | edges_to_remove.append((u, v)) 347 | 348 | self.remove_edges_from(edges_to_remove) 349 | 350 | self._remove_layer(layer) 351 | 352 | 353 | class DiMultinet(Multinet, nx.DiGraph): 354 | """ 355 | Directed Multiplex network class. 356 | """ 357 | 358 | def __init__(self, container_id='multiplex'): 359 | """Initialize a Multinet with empty layer list. 360 | """ 361 | nx.DiGraph.__init__(self) 362 | self._cid = container_id 363 | self.nx_base = nx.DiGraph 364 | self.graph['layers'] = [] 365 | 366 | 367 | def to_undirected(self): 368 | """ Transform DiMultinet to Multinet. 369 | """ 370 | g = Multinet() 371 | g.add_nodes_from(self.nodes()) 372 | g.graph['layers'] = self.layers() 373 | for u,v in self.edges(): 374 | for layer in self[u][v][self.cid]: 375 | g.aggregate_edge(u,v,layer,self[u][v][self.cid][layer]) 376 | return g 377 | -------------------------------------------------------------------------------- /multinet/information_measure.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | 3 | import dit 4 | import itertools 5 | import multinet as mn 6 | 7 | from collections import Counter 8 | 9 | 10 | def extract_count(g, layers, ignore_self_loop=True): 11 | c = Counter() 12 | for u,v in g.edges(): 13 | if ignore_self_loop and u == v: 14 | continue 15 | word = '' 16 | for layer in layers: 17 | if layer in g[u][v][g.cid]: 18 | word += '1' 19 | else: 20 | word += '0' 21 | c[word] += 1 22 | 23 | nnode = g.number_of_nodes() 24 | nedge = g.number_of_edges() 25 | 26 | total_position = nnode * (nnode - 1) 27 | 28 | if g.is_directed(): 29 | non_edge = total_position - nedge 30 | else: 31 | non_edge = total_position / 2 - nedge 32 | 33 | if not ignore_self_loop: 34 | non_edge += nnode 35 | 36 | word = '0'*len(layers) 37 | c[word] += non_edge 38 | 39 | # Add count 0 for non-existed config. 40 | for i in range(2 ** len(layers)): 41 | word = bin(i)[2:].zfill(len(layers)) 42 | if word not in c: 43 | c[word] = 0 44 | 45 | return c 46 | 47 | 48 | def mutual_information(g,layers): 49 | counts = extract_count(g, layers) 50 | total = sum(counts.values()) 51 | probs = [ (key, value / total) 52 | for key, value in counts.items() ] 53 | dist = dit.Distribution(*zip(*probs)) 54 | 55 | if len(layers) == 2: 56 | return dit.shannon.mutual_information(dist,[0],[1]) 57 | else: 58 | raise Exception("Not implemented.") 59 | 60 | 61 | def iinf(g1,g2,layers=None): 62 | te = {} 63 | if not layers: 64 | layers = set(g1.layers())&set(g2.layers()) 65 | for layer1,layer2 in itertools.combinations(layers,2): 66 | if g1.is_directed() != g2.is_directed(): 67 | raise Exception('Must both be directed or undirected!') 68 | if g1.is_directed(): 69 | tg = mn.DiMultinet() 70 | else: 71 | tg = mn.Multinet() 72 | tg.add_layer(g1.sub_layer(layer1),'t1_'+layer1) 73 | tg.add_layer(g1.sub_layer(layer2),'t1_'+layer2) 74 | tg.add_layer(g2.sub_layer(layer1),'t2_'+layer1) 75 | tg.add_layer(g2.sub_layer(layer2),'t2_'+layer2) 76 | tlayers = ['t1_'+layer1,'t1_'+layer2,'t2_'+layer1,'t2_'+layer2] 77 | counts = extract_count(tg,tlayers) 78 | total = float(sum(counts.values())) 79 | probs = [ (key, value / total) 80 | for key, value in counts.items() ] 81 | dist = dit.Distribution(*zip(*probs)) 82 | cmia = (dit.shannon.conditional_entropy(dist, [2], [0]) 83 | - dit.shannon.conditional_entropy(dist, [2],[0, 1])) 84 | cmib = (dit.shannon.conditional_entropy(dist, [3], [1]) 85 | - dit.shannon.conditional_entropy(dist, [3], [0, 1])) 86 | te[(layer2,layer1)] = cmia 87 | te[(layer1,layer2)] = cmib 88 | 89 | return te 90 | 91 | 92 | def jaccard_distance(g,layer1,layer2): 93 | counts = extract_count(g,(layer1,layer2)) 94 | union = counts['01'] + counts['10'] + counts['11'] 95 | if union == 0: 96 | return 0.0 97 | return float(counts['11'])/union 98 | 99 | 100 | def hamming_distance(g,layer1,layer2): 101 | counts = extract_count(g,(layer1,layer2)) 102 | return counts['01'] + counts['10'] 103 | -------------------------------------------------------------------------------- /multinet/l_core.py: -------------------------------------------------------------------------------- 1 | """Calculate the l-core of a multiplex network. 2 | """ 3 | 4 | import networkx as nx 5 | 6 | def l_core(mg, l): 7 | """Return the l-core of mg. 8 | 9 | """ 10 | import copy 11 | core = copy.deepcopy(mg) 12 | 13 | for u,v in mg.edges(): 14 | if len(mg[u][v][mg.cid]) < l: 15 | core.remove_edge(u,v) 16 | 17 | core.remove_nodes_from(nx.isolates(core)) 18 | core.remove_empty_layers() 19 | 20 | return core 21 | -------------------------------------------------------------------------------- /multinet/random_models.py: -------------------------------------------------------------------------------- 1 | """ 2 | A few multiplex configuration models. 3 | 4 | """ 5 | from __future__ import division 6 | 7 | import random 8 | 9 | import networkx as nx 10 | import multinet as mn 11 | 12 | from multinet.bipartite import bipartize 13 | from multinet.bipartite import bipartite_sets 14 | from multinet.bipartite import reconstruct_from_bipartite 15 | 16 | def multiplex_configuration_bipartite(mg, seed=None): 17 | """Bipartite configuration model. 18 | First convert the multiplex network to a bipartite graph using layer-edge 19 | view. Then run a configuraion model on the bipartite graph and reconstruct 20 | a multiplex network. This configuration model will preserve the aggregated 21 | network and the number of layers each edge sits on. 22 | 23 | Parameters 24 | ---------- 25 | mg : Multinet 26 | Multiplex network to be configured. 27 | 28 | seed : object 29 | Seed for the configuration model. 30 | 31 | Return 32 | ------ 33 | A new Multinet instance. 34 | 35 | """ 36 | bg = bipartize(mg,'e') 37 | top, bottom = bipartite_sets(bg) 38 | 39 | top = list(top) 40 | bottom = list(bottom) 41 | 42 | degree_getter = lambda x: bg.degree(x) 43 | dtop = map(degree_getter,top) 44 | dbottom = map(degree_getter,bottom) 45 | 46 | rbg = nx.bipartite.configuration_model( 47 | dtop, dbottom, create_using=nx.Graph(), seed=seed) 48 | rtop,rbottom = bipartite_sets(rbg) 49 | 50 | keys = list(rtop)+list(rbottom) 51 | values = list(top)+list(bottom) 52 | mapping = dict(zip(keys,values)) 53 | 54 | nrbg = nx.relabel_nodes(rbg,mapping) 55 | 56 | if mg.is_directed(): 57 | create_using = mn.DiMultinet() 58 | else: 59 | create_using = mn.Multinet() 60 | nmg = reconstruct_from_bipartite(nrbg, create_using=create_using) 61 | 62 | return nmg 63 | 64 | 65 | def multiplex_configuration_independent(mg, seed=None, include_all=False): 66 | """Run configuration model independently for each layer. 67 | 68 | Parameters 69 | ---------- 70 | mg : Multinet 71 | Multiplex network to be configured. 72 | 73 | seed : object 74 | Seed for the configuration model. 75 | 76 | Return 77 | ------ 78 | A new Multinet instance. 79 | 80 | 81 | """ 82 | layers = mg.layers() 83 | 84 | r = random.Random() 85 | r.seed(seed) 86 | 87 | directed = mg.is_directed() 88 | if directed: 89 | nmg = mn.DiMultinet() 90 | else: 91 | nmg = mn.Multinet() 92 | 93 | remove_isolates = not include_all 94 | 95 | for layer in layers: 96 | sg = mg.sub_layer(layer, remove_isolates=remove_isolates) 97 | nodes = sg.nodes() 98 | if directed: 99 | in_degs = [sg.in_degree(n) for n in nodes] 100 | out_degs = [sg.out_degree(n) for n in nodes] 101 | rsg = nx.directed_configuration_model( 102 | in_degs, out_degs, create_using=nx.DiGraph(), seed=r) 103 | else: 104 | degs = [sg.degree(n) for n in nodes] 105 | rsg = nx.configuration_model( 106 | degs, create_using=nx.Graph(), seed=r) 107 | rnodes = rsg.nodes() 108 | mapping = dict(zip(rnodes, nodes)) 109 | nrsg = nx.relabel_nodes(rsg, mapping) 110 | nmg.add_layer(nrsg ,layer) 111 | 112 | return nmg 113 | 114 | 115 | def multiplex_erdos_renyi(mg, seed=None, include_all=True): 116 | """Return a Multinet such that each layer is an Erdos-Renyi network with 117 | same p as the original Multinet given. 118 | 119 | Parameters 120 | ---------- 121 | mg : Multinet 122 | Multiplex network to be configured. 123 | 124 | seed : object 125 | Seed for the model. 126 | 127 | Return 128 | ------ 129 | A new Multinet instance. 130 | 131 | 132 | """ 133 | layers = mg.layers() 134 | 135 | r = random.Random() 136 | r.seed(seed) 137 | 138 | directed = mg.is_directed() 139 | if directed: 140 | nmg = mn.DiMultinet() 141 | else: 142 | nmg = mn.Multinet() 143 | 144 | remove_isolates = not include_all 145 | 146 | for layer in layers: 147 | sg = mg.sub_layer(layer, remove_isolates=remove_isolates) 148 | nodes = sg.nodes() 149 | nnode = sg.number_of_nodes() 150 | nedge = sg.number_of_edges() 151 | if directed: 152 | p = nedge / (nnode * (nnode - 1)) 153 | else: 154 | p = 2 * nedge/ (nnode * (nnode - 1)) 155 | rsg = nx.erdos_renyi_graph( 156 | nnode, p, seed=r, directed=directed) 157 | rnodes = rsg.nodes() 158 | mapping = dict(zip(rnodes, nodes)) 159 | nrsg = nx.relabel_nodes(rsg, mapping) 160 | nmg.add_layer(nrsg, layer) 161 | 162 | return nmg 163 | -------------------------------------------------------------------------------- /multinet/shortest_path.py: -------------------------------------------------------------------------------- 1 | """Functions related to shortest paths. 2 | 3 | """ 4 | from __future__ import division 5 | 6 | import itertools 7 | import networkx as nx 8 | 9 | def all_pairs_k_layer_shortest_path_length(mg, k): 10 | """Calculate all the k-layer shortest path lengths. 11 | 12 | Parameters: 13 | ----------- 14 | mg: Multinet 15 | Multiplex network to calculate. 16 | 17 | k: int 18 | Number of layers allowed to use when k is positive. 19 | Number of layers not allowed to use when k is negative. 20 | 21 | 22 | Returns: 23 | -------- 24 | A dictionary whose key is a source-destination pair 25 | and its value is the corresponding k-layer shortest path length. 26 | 27 | """ 28 | shortest_path_lengths = {} 29 | layers = mg.layers() 30 | nodes = mg.nodes() 31 | 32 | for pair in itertools.permutations(nodes, 2): 33 | shortest_path_lengths[pair] = float('inf') 34 | 35 | # syntax sugar to allow convenient operation. 36 | if k <= 0: 37 | k = mg.number_of_layers() + k 38 | 39 | for subnet in itertools.combinations(layers, k): 40 | sg = mg.sub_layers(subnet) 41 | length = nx.all_pairs_shortest_path_length(sg) 42 | for src, shortests in length: 43 | for dst in shortests: 44 | if src == dst: 45 | continue 46 | if shortests[dst] < shortest_path_lengths[(src, dst)]: 47 | shortest_path_lengths[(src, dst)] = shortests[dst] 48 | 49 | return shortest_path_lengths 50 | 51 | 52 | def k_layer_reachability(mg,k): 53 | """k-layer reachability 54 | 55 | Parameters: 56 | ----------- 57 | mg: Multinet 58 | Multiplex network to calculate. 59 | 60 | k: int 61 | Number of layers allowed to use. 62 | 63 | """ 64 | lengths = all_pairs_k_layer_shortest_path_length(mg, k) 65 | from collections import Counter 66 | lc = Counter(lengths.values()) 67 | unreachable = lc[float('inf')] 68 | total = sum(lc.values()) 69 | 70 | return 1-(unreachable/total) 71 | 72 | 73 | def k_layer_diameter(mg,k): 74 | """k-layer diameter 75 | 76 | Parameters: 77 | ----------- 78 | mg: Multinet 79 | Multiplex network to calculate. 80 | 81 | k: int 82 | Number of layers allowed to use. 83 | 84 | """ 85 | lengths = all_pairs_k_layer_shortest_path_length(mg,k) 86 | from collections import Counter 87 | lc = Counter(lengths.values()) 88 | reachable = filter(lambda x:x 0: 90 | return max(reachable) 91 | else: 92 | return 0 93 | 94 | 95 | def harmonic_mean_shortest_path_length(mg, source=None, target=None): 96 | """Harmonic mean shortest path length in multiplex network. 97 | The inverse of harmonic mean of shortest path length in each layer 98 | between two nodes. 99 | $$L(u, v) = 1 / \sum_l (1 / L_l(u, v))$$ 100 | $L_l(u, v)$ is the shortest path length between node $u$ and $v$ in 101 | layer $l$. 102 | 103 | Parameters: 104 | ----------- 105 | mg: Multinet 106 | Multiplex network of interest. 107 | 108 | source: node or list 109 | Node of a list of nodes as source. 110 | 111 | target: node or list 112 | Node of a list of nodes as target. 113 | 114 | Returns: 115 | -------- 116 | A dict keyed by (source, target) pair. 117 | """ 118 | #TODO: implement for specified source and target. 119 | if not (source and target): 120 | from collections import Counter 121 | sum_harmonic = Counter() 122 | for layer in mg.layers(): 123 | sg = mg.sub_layer(layer) 124 | shortest_paths_length = nx.all_pairs_shortest_path_length(sg) 125 | for u, spl in shortest_paths_length: 126 | for v in spl: 127 | if u != v: 128 | sum_harmonic[(u,v)] += 1./spl[v] 129 | ret = {} 130 | for pair in sum_harmonic: 131 | ret[pair] = 1./sum_harmonic[pair] 132 | return ret -------------------------------------------------------------------------------- /multinet/tests/test_bipartite.py: -------------------------------------------------------------------------------- 1 | import multinet as mn 2 | import networkx as nx 3 | 4 | g1 = nx.Graph() 5 | nodes = ['A', 'B', 'C', 'D', 'E'] 6 | edges = [('A', 'B'), ('B', 'C'), ('C', 'D'), ('D', 'E'), ('E', 'A')] 7 | g1.add_nodes_from(nodes) 8 | g1.add_edges_from(edges) 9 | 10 | g2 = nx.Graph() 11 | nodes = ['A', 'B', 'C', 'D', 'E'] 12 | edges = [('A', 'C'), ('B', 'D'), ('C', 'E'), ('D', 'A'), ('E', 'B')] 13 | g2.add_nodes_from(nodes) 14 | g2.add_edges_from(edges) 15 | 16 | g3 = nx.Graph() 17 | nodes = ['A', 'B', 'C', 'D', 'E'] 18 | edges = [('A', 'B'), ('A', 'C'), ('A', 'D'), ('A', 'E'), ('B', 'D'), ('C', 'E')] 19 | g3.add_nodes_from(nodes) 20 | g3.add_edges_from(edges) 21 | 22 | mg = mn.Multinet() 23 | 24 | mg.add_layer(g1, '1') 25 | mg.add_layer(g2, '2') 26 | mg.add_layer(g3, '3') 27 | 28 | 29 | class TestBipartite(object): 30 | 31 | 32 | def test_bipartize(self): 33 | bg = mn.bipartize(mg, 'n') 34 | assert (bg.number_of_nodes() == 35 | mg.number_of_layers() + 36 | mg.number_of_nodes()) 37 | bg = mn.bipartize(mg, 'e') 38 | assert (bg.number_of_nodes() == 39 | mg.number_of_layers() + 40 | mg.number_of_edges()) 41 | 42 | 43 | class TestReconstruct(object): 44 | 45 | 46 | def test_reconstruct(self): 47 | bg = mn.bipartize(mg, 'e') 48 | nmg = mn.reconstruct_from_bipartite(bg, mn.Multinet()) 49 | assert nmg.number_of_layers() == mg.number_of_layers() 50 | assert nmg.number_of_nodes() == mg.number_of_nodes() 51 | assert nmg.number_of_edges() == mg.number_of_edges() 52 | 53 | -------------------------------------------------------------------------------- /multinet/tests/test_classes.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test the Multinet Class. 3 | """ 4 | import multinet as mn 5 | import networkx as nx 6 | 7 | 8 | class TestMultinet(object): 9 | 10 | 11 | def test_build_multinet(self): 12 | """ 13 | Test building Multinet objects. 14 | """ 15 | mg = mn.Multinet() 16 | 17 | assert mg.is_directed() == False 18 | 19 | mg.add_edge(0, 1, 'L1') 20 | mg.add_edge(0, 1, 'L2') 21 | mg.add_edge(1, 0, 'L2') 22 | mg.add_edge(1, 2, 'L2') 23 | 24 | assert 'L1' in mg.layers() 25 | assert 'L2' in mg.layers() 26 | 27 | assert len(mg.edgelets) == 3 28 | 29 | assert mg.number_of_nodes() == 3 30 | assert mg.number_of_edges() == 2 31 | assert mg.number_of_layers() == 2 32 | assert mg.number_of_edgelets() == 3 33 | 34 | # Remove non-existed edge. 35 | mg.remove_edgelet(2, 3, 'L3') 36 | 37 | mg.remove_edgelet(0, 1, 'L2') 38 | assert mg.number_of_nodes() == 3 39 | assert mg.number_of_edges() == 2 40 | assert mg.number_of_layers() == 2 41 | assert mg.number_of_edgelets() == 2 42 | 43 | mg.remove_edgelet(0, 1, 'L1') 44 | assert mg.number_of_nodes() == 3 45 | assert mg.number_of_edges() == 1 46 | assert mg.number_of_layers() == 2 47 | assert mg.number_of_edgelets() == 1 48 | 49 | assert len(mg.empty_layers()) == 1 50 | 51 | mg.remove_empty_layers() 52 | 53 | assert mg.number_of_layers() == 1 54 | 55 | 56 | def test_aggregate_edge(self): 57 | mg = mn.Multinet() 58 | 59 | mg.add_edge(0, 1, 'L1', weight=5) 60 | mg.add_edge(1, 2, 'L2', weight=6) 61 | 62 | assert mg[0][1][mg.cid]['L1'] == 5 63 | assert mg[1][2][mg.cid]['L2'] == 6 64 | 65 | mg.add_edge(0, 1, 'L1', weight=10) 66 | assert mg[0][1][mg.cid]['L1'] == 10 67 | 68 | mg.aggregate_edge(0, 1, 'L1', weight=5) 69 | assert mg[0][1][mg.cid]['L1'] == 15 70 | 71 | mg.aggregate_edge(2, 3, 'L2', weight=7) 72 | assert mg[2][3][mg.cid]['L2'] == 7 73 | 74 | 75 | def test_sub_layer(self): 76 | mg = mn.Multinet() 77 | 78 | mg.add_edge(0, 1, 'L1', weight=5) 79 | mg.add_edge(1, 2, 'L2', weight=6) 80 | 81 | sg = mg.sub_layer('L1') 82 | assert type(sg) == nx.Graph 83 | assert sg.number_of_nodes() == 3 84 | assert sg.number_of_edges() == 1 85 | 86 | sg = mg.sub_layer('L2', remove_isolates=True) 87 | assert type(sg) == nx.Graph 88 | assert sg.number_of_nodes() == 2 89 | assert sg.number_of_edges() == 1 90 | 91 | 92 | def test_sub_layers(self): 93 | mg = mn.Multinet() 94 | 95 | mg.add_edge(0, 1, 'L1', weight=5) 96 | mg.add_edge(1, 2, 'L2', weight=6) 97 | mg.add_edge(1, 2, 'L3', weight=2) 98 | 99 | sg = mg.sub_layers(['L1', 'L2']) 100 | assert type(sg) == mn.Multinet 101 | assert sg.number_of_nodes() == 3 102 | assert sg.number_of_edges() == 2 103 | assert sg.number_of_layers() == 2 104 | 105 | sg = mg.sub_layers(['L2', 'L3'], remove_isolates=True) 106 | assert type(sg) == mn.Multinet 107 | assert sg.number_of_nodes() == 2 108 | assert sg.number_of_edges() == 1 109 | assert sg.number_of_layers() == 2 110 | 111 | 112 | def test_aggregated(self): 113 | mg = mn.Multinet() 114 | 115 | mg.add_edge(0, 1, 'L1', weight=5) 116 | mg.add_edge(1, 2, 'L2', weight=6) 117 | mg.add_edge(1, 2, 'L3', weight=2) 118 | 119 | ag = mg.aggregated() 120 | assert type(ag) == nx.Graph 121 | assert ag.number_of_nodes() == 3 122 | assert ag.number_of_edges() == 2 123 | 124 | assert ag[1][2]['weight'] == 8 125 | assert ag[1][2]['nlayer'] == 2 126 | 127 | 128 | def test_merge_layers(self): 129 | mg = mn.Multinet() 130 | 131 | mg.add_edge(0, 1, 'L1', weight=5) 132 | mg.add_edge(1, 2, 'L2', weight=6) 133 | mg.add_edge(1, 2, 'L3', weight=2) 134 | 135 | mg.merge_layers(['L1', 'L2']) 136 | assert 'L1' not in mg.layers() 137 | assert 'L2' not in mg.layers() 138 | assert 'L1_L2' in mg.layers() 139 | assert mg.number_of_layers() == 2 140 | assert mg.number_of_nodes() == 3 141 | assert mg.number_of_edges() == 2 142 | 143 | assert mg[0][1][mg.cid]['L1_L2'] == 5 144 | assert mg[1][2][mg.cid]['L1_L2'] == 6 145 | 146 | mg = mn.Multinet() 147 | 148 | mg.add_edge(0, 1, 'L1', weight=5) 149 | mg.add_edge(1, 2, 'L2', weight=6) 150 | mg.add_edge(1, 2, 'L3', weight=2) 151 | 152 | mg.merge_layers(['L2', 'L3'], new_name='LN') 153 | assert 'L2' not in mg.layers() 154 | assert 'L3' not in mg.layers() 155 | assert 'LN' in mg.layers() 156 | assert mg.number_of_layers() == 2 157 | assert mg.number_of_nodes() == 3 158 | assert mg.number_of_edges() == 2 159 | 160 | assert mg[0][1][mg.cid]['L1'] == 5 161 | assert mg[1][2][mg.cid]['LN'] == 8 162 | 163 | 164 | def test_add_layer(self): 165 | mg = mn.Multinet() 166 | 167 | mg.add_edge(0, 1, 'L1', weight=5) 168 | mg.add_edge(1, 2, 'L2', weight=6) 169 | 170 | sg = nx.Graph() 171 | sg.add_edge(1, 2, weight=7) 172 | sg.add_edge(2, 3) 173 | 174 | mg.add_layer(sg, 'L3') 175 | assert mg.number_of_nodes() == 4 176 | assert mg.number_of_edges() == 3 177 | assert mg.number_of_layers() == 3 178 | 179 | assert mg[1][2][mg.cid]['L2'] == 6 180 | assert mg[1][2][mg.cid]['L3'] == 7 181 | assert mg[2][3][mg.cid]['L3'] == 1 182 | 183 | 184 | def test_remove_layer(self): 185 | mg = mn.Multinet() 186 | 187 | mg.add_edge(0, 1, 'L1', weight=5) 188 | mg.add_edge(1, 2, 'L2', weight=6) 189 | mg.add_edge(1, 2, 'L3', weight=2) 190 | 191 | mg.remove_layer('L3') 192 | assert mg.number_of_nodes() == 3 193 | assert mg.number_of_edges() == 2 194 | assert mg.number_of_layers() == 2 195 | 196 | mg = mn.Multinet() 197 | 198 | mg.add_edge(0, 1, 'L1', weight=5) 199 | mg.add_edge(1, 2, 'L2', weight=6) 200 | mg.add_edge(1, 2, 'L3', weight=2) 201 | 202 | mg.remove_layer('L1') 203 | assert mg.number_of_nodes() == 3 204 | assert mg.number_of_edges() == 1 205 | assert mg.number_of_layers() == 2 206 | 207 | 208 | class TestDiMultinet(object): 209 | 210 | 211 | def test_build_dimultinet(self): 212 | """ 213 | Test building Multinet objects. 214 | """ 215 | mg = mn.DiMultinet() 216 | 217 | assert mg.is_directed() == True 218 | 219 | mg.add_edge(0, 1, 'L1') 220 | mg.add_edge(0, 1, 'L2') 221 | mg.add_edge(1, 0, 'L2') 222 | mg.add_edge(1, 2, 'L2') 223 | 224 | assert 'L1' in mg.layers() 225 | assert 'L2' in mg.layers() 226 | 227 | assert len(mg.edgelets) == 4 228 | 229 | assert mg.number_of_nodes() == 3 230 | assert mg.number_of_edges() == 3 231 | assert mg.number_of_layers() == 2 232 | assert mg.number_of_edgelets() == 4 233 | 234 | # Remove non-existed edge. 235 | mg.remove_edgelet(2, 3, 'L3') 236 | 237 | mg.remove_edgelet(0, 1, 'L2') 238 | assert mg.number_of_nodes() == 3 239 | assert mg.number_of_edges() == 3 240 | assert mg.number_of_layers() == 2 241 | assert mg.number_of_edgelets() == 3 242 | 243 | mg.remove_edgelet(0, 1, 'L1') 244 | assert mg.number_of_nodes() == 3 245 | assert mg.number_of_edges() == 2 246 | assert mg.number_of_layers() == 2 247 | assert mg.number_of_edgelets() == 2 248 | 249 | assert len(mg.empty_layers()) == 1 250 | 251 | mg.remove_empty_layers() 252 | 253 | assert mg.number_of_layers() == 1 254 | 255 | 256 | def test_aggregate_edge(self): 257 | mg = mn.DiMultinet() 258 | 259 | mg.add_edge(0, 1, 'L1', weight=5) 260 | mg.add_edge(1, 2, 'L2', weight=6) 261 | 262 | assert mg[0][1][mg.cid]['L1'] == 5 263 | assert mg[1][2][mg.cid]['L2'] == 6 264 | 265 | mg.add_edge(0, 1, 'L1', weight=10) 266 | assert mg[0][1][mg.cid]['L1'] == 10 267 | 268 | mg.aggregate_edge(0, 1, 'L1', weight=5) 269 | assert mg[0][1][mg.cid]['L1'] == 15 270 | 271 | mg.aggregate_edge(2, 3, 'L2', weight=7) 272 | assert mg[2][3][mg.cid]['L2'] == 7 273 | 274 | 275 | def test_sub_layer(self): 276 | mg = mn.DiMultinet() 277 | 278 | mg.add_edge(0, 1, 'L1', weight=5) 279 | mg.add_edge(1, 2, 'L2', weight=6) 280 | 281 | sg = mg.sub_layer('L1') 282 | assert type(sg) == nx.DiGraph 283 | assert sg.number_of_nodes() == 3 284 | assert sg.number_of_edges() == 1 285 | 286 | sg = mg.sub_layer('L2', remove_isolates=True) 287 | assert type(sg) == nx.DiGraph 288 | assert sg.number_of_nodes() == 2 289 | assert sg.number_of_edges() == 1 290 | 291 | 292 | def test_sub_layers(self): 293 | mg = mn.DiMultinet() 294 | 295 | mg.add_edge(0, 1, 'L1', weight=5) 296 | mg.add_edge(1, 2, 'L2', weight=6) 297 | mg.add_edge(1, 2, 'L3', weight=2) 298 | 299 | sg = mg.sub_layers(['L1', 'L2']) 300 | assert type(sg) == mn.DiMultinet 301 | assert sg.number_of_nodes() == 3 302 | assert sg.number_of_edges() == 2 303 | assert sg.number_of_layers() == 2 304 | 305 | sg = mg.sub_layers(['L2', 'L3'], remove_isolates=True) 306 | assert type(sg) == mn.DiMultinet 307 | assert sg.number_of_nodes() == 2 308 | assert sg.number_of_edges() == 1 309 | assert sg.number_of_layers() == 2 310 | 311 | 312 | def test_aggregated(self): 313 | mg = mn.DiMultinet() 314 | 315 | mg.add_edge(0, 1, 'L1', weight=5) 316 | mg.add_edge(1, 2, 'L2', weight=6) 317 | mg.add_edge(1, 2, 'L3', weight=2) 318 | 319 | ag = mg.aggregated() 320 | assert type(ag) == nx.DiGraph 321 | assert ag.number_of_nodes() == 3 322 | assert ag.number_of_edges() == 2 323 | 324 | assert ag[1][2]['weight'] == 8 325 | assert ag[1][2]['nlayer'] == 2 326 | 327 | 328 | def test_merge_layers(self): 329 | mg = mn.DiMultinet() 330 | 331 | mg.add_edge(0, 1, 'L1', weight=5) 332 | mg.add_edge(1, 2, 'L2', weight=6) 333 | mg.add_edge(1, 2, 'L3', weight=2) 334 | 335 | mg.merge_layers(['L1', 'L2']) 336 | assert 'L1' not in mg.layers() 337 | assert 'L2' not in mg.layers() 338 | assert 'L1_L2' in mg.layers() 339 | assert mg.number_of_layers() == 2 340 | assert mg.number_of_nodes() == 3 341 | assert mg.number_of_edges() == 2 342 | 343 | assert mg[0][1][mg.cid]['L1_L2'] == 5 344 | assert mg[1][2][mg.cid]['L1_L2'] == 6 345 | 346 | mg = mn.DiMultinet() 347 | 348 | mg.add_edge(0, 1, 'L1', weight=5) 349 | mg.add_edge(1, 2, 'L2', weight=6) 350 | mg.add_edge(1, 2, 'L3', weight=2) 351 | 352 | mg.merge_layers(['L2', 'L3'], new_name='LN') 353 | assert 'L2' not in mg.layers() 354 | assert 'L3' not in mg.layers() 355 | assert 'LN' in mg.layers() 356 | assert mg.number_of_layers() == 2 357 | assert mg.number_of_nodes() == 3 358 | assert mg.number_of_edges() == 2 359 | 360 | assert mg[0][1][mg.cid]['L1'] == 5 361 | assert mg[1][2][mg.cid]['LN'] == 8 362 | 363 | 364 | def test_add_layer(self): 365 | mg = mn.DiMultinet() 366 | 367 | mg.add_edge(0, 1, 'L1', weight=5) 368 | mg.add_edge(1, 2, 'L2', weight=6) 369 | 370 | sg = nx.Graph() 371 | sg.add_edge(1, 2, weight=7) 372 | sg.add_edge(2, 3) 373 | 374 | mg.add_layer(sg, 'L3') 375 | assert mg.number_of_nodes() == 4 376 | assert mg.number_of_edges() == 3 377 | assert mg.number_of_layers() == 3 378 | 379 | assert mg[1][2][mg.cid]['L2'] == 6 380 | assert mg[1][2][mg.cid]['L3'] == 7 381 | assert mg[2][3][mg.cid]['L3'] == 1 382 | 383 | 384 | def test_remove_layer(self): 385 | mg = mn.DiMultinet() 386 | 387 | mg.add_edge(0, 1, 'L1', weight=5) 388 | mg.add_edge(1, 2, 'L2', weight=6) 389 | mg.add_edge(1, 2, 'L3', weight=2) 390 | 391 | mg.remove_layer('L3') 392 | assert mg.number_of_nodes() == 3 393 | assert mg.number_of_edges() == 2 394 | assert mg.number_of_layers() == 2 395 | 396 | mg = mn.DiMultinet() 397 | 398 | mg.add_edge(0, 1, 'L1', weight=5) 399 | mg.add_edge(1, 2, 'L2', weight=6) 400 | mg.add_edge(1, 2, 'L3', weight=2) 401 | 402 | mg.remove_layer('L1') 403 | assert mg.number_of_nodes() == 3 404 | assert mg.number_of_edges() == 1 405 | assert mg.number_of_layers() == 2 406 | 407 | 408 | def test_to_undirected(self): 409 | mg = mn.DiMultinet() 410 | 411 | mg.add_edge(0, 1, 'L1', weight=5) 412 | mg.add_edge(1, 2, 'L2', weight=6) 413 | mg.add_edge(2, 1, 'L3', weight=2) 414 | 415 | assert mg.number_of_nodes() == 3 416 | assert mg.number_of_edges() == 3 417 | assert mg.number_of_layers() == 3 418 | 419 | nmg = mg.to_undirected() 420 | assert nmg.number_of_nodes() == 3 421 | assert nmg.number_of_edges() == 2 422 | assert nmg.number_of_layers() == 3 423 | -------------------------------------------------------------------------------- /multinet/tests/test_information_measure.py: -------------------------------------------------------------------------------- 1 | import multinet as mn 2 | import networkx as nx 3 | 4 | g1 = nx.Graph() 5 | dg1 = nx.DiGraph() 6 | nodes = ['A', 'B', 'C', 'D', 'E'] 7 | edges = [('A', 'B'), ('B', 'C'), ('C', 'D'), ('D', 'E'), ('E', 'A')] 8 | g1.add_nodes_from(nodes) 9 | g1.add_edges_from(edges) 10 | dg1.add_nodes_from(nodes) 11 | dg1.add_edges_from(edges) 12 | 13 | g2 = nx.Graph() 14 | dg2 = nx.DiGraph() 15 | nodes = ['A', 'B', 'C', 'D', 'E'] 16 | edges = [('A', 'C'), ('B', 'D'), ('C', 'E'), ('D', 'A'), ('E', 'B')] 17 | g2.add_nodes_from(nodes) 18 | g2.add_edges_from(edges) 19 | dg2.add_nodes_from(nodes) 20 | dg2.add_edges_from(edges) 21 | 22 | g3 = nx.Graph() 23 | dg3 = nx.DiGraph() 24 | nodes = ['A', 'B', 'C', 'D', 'E'] 25 | edges = [('A', 'B'), ('A', 'C'), ('A', 'D'), ('A', 'E'), ('B', 'D'), ('C', 'E')] 26 | g3.add_nodes_from(nodes) 27 | g3.add_edges_from(edges) 28 | dg3.add_nodes_from(nodes) 29 | dg3.add_edges_from(edges) 30 | 31 | mg = mn.Multinet() 32 | mg.add_layer(g1, '1') 33 | mg.add_layer(g2, '2') 34 | mg.add_layer(g3, '3') 35 | 36 | dmg = mn.DiMultinet() 37 | dmg.add_layer(dg1, '1') 38 | dmg.add_layer(dg2, '2') 39 | dmg.add_layer(dg3, '3') 40 | 41 | g4 = nx.Graph() 42 | nodes = ['A', 'B', 'C', 'D', 'E'] 43 | edges = [] 44 | g4.add_nodes_from(nodes) 45 | g4.add_edges_from(edges) 46 | 47 | mg2 = mn.Multinet() 48 | mg2.add_layer(g1, '1') 49 | mg2.add_layer(g4, '2') 50 | mg2.add_layer(g1, '3') 51 | 52 | 53 | class TestExtractCount(object): 54 | 55 | 56 | def test_extract_count(self): 57 | count = mn.extract_count(mg, ['1', '2']) 58 | assert len(count) == 4 59 | assert count['00'] == 0 60 | assert count['01'] == 5 61 | assert count['10'] == 5 62 | assert count['11'] == 0 63 | 64 | count = mn.extract_count(mg, ['1', '1']) 65 | assert len(count) == 4 66 | assert count['00'] == 5 67 | assert count['01'] == 0 68 | assert count['10'] == 0 69 | assert count['11'] == 5 70 | 71 | count = mn.extract_count(mg, ['1', '2'], False) 72 | assert len(count) == 4 73 | assert count['00'] == 5 74 | assert count['01'] == 5 75 | assert count['10'] == 5 76 | assert count['11'] == 0 77 | 78 | count = mn.extract_count(dmg, ['1', '2']) 79 | assert len(count) == 4 80 | assert count['00'] == 10 81 | assert count['01'] == 5 82 | assert count['10'] == 5 83 | assert count['11'] == 0 84 | 85 | count = mn.extract_count(dmg, ['1', '2'], False) 86 | assert len(count) == 4 87 | assert count['00'] == 15 88 | assert count['01'] == 5 89 | assert count['10'] == 5 90 | assert count['11'] == 0 91 | 92 | 93 | class TestMutualInformation(object): 94 | 95 | 96 | def test_mutual_infomation(self): 97 | assert mn.mutual_information(mg, ['1', '1']) == 1 98 | assert mn.mutual_information(mg, ['1', '2']) == 1 99 | 100 | 101 | class TestIINF(object): 102 | 103 | 104 | def test_iinf(self): 105 | assert mn.iinf(mg, mg2)[('1', '2')] == 0 106 | assert mn.iinf(mg, mg2)[('2', '1')] == 0 107 | 108 | assert mn.iinf(mg2, mg)[('1', '2')] == 1 109 | assert mn.iinf(mg2, mg)[('2', '1')] == 0 110 | 111 | assert mn.iinf(mg2, mg, layers=['1', '2'])[('1', '2')] == 1 112 | assert mn.iinf(mg2, mg, layers=['1', '2'])[('2', '1')] == 0 113 | -------------------------------------------------------------------------------- /multinet/tests/test_random_models.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | import multinet as mn 4 | import networkx as nx 5 | 6 | nlayers = 5 7 | nnodes = 20 8 | 9 | mg = mn.Multinet() 10 | for layer in range(nlayers): 11 | p = random.uniform(0.3,0.6) 12 | layer_name = '%d' % layer 13 | g = nx.erdos_renyi_graph(nnodes, p) 14 | mg.add_layer(g, layer_name) 15 | 16 | dmg = mn.DiMultinet() 17 | for layer in range(nlayers): 18 | p = random.uniform(0.3,0.6) 19 | layer_name = '%d' % layer 20 | g = nx.erdos_renyi_graph(nnodes, p) 21 | mg.add_layer(g, layer_name) 22 | 23 | 24 | class TestErdosRenyi(object): 25 | 26 | 27 | def test_undirected(self): 28 | seed = 42 29 | 30 | rmg = mn.multiplex_erdos_renyi(mg, seed=seed) 31 | rmg = mn.multiplex_erdos_renyi(mg) 32 | 33 | 34 | def test_directed(self): 35 | seed = 42 36 | 37 | rmg = mn.multiplex_erdos_renyi(dmg, seed=seed) 38 | rmg = mn.multiplex_erdos_renyi(dmg) 39 | 40 | 41 | class TestIndependentConfig(object): 42 | 43 | 44 | def test_undirected(self): 45 | seed = 42 46 | rmg = mn.multiplex_configuration_independent(mg, seed=seed) 47 | rmg = mn.multiplex_configuration_independent(mg) 48 | 49 | 50 | def test_directed(self): 51 | seed = 42 52 | rmg = mn.multiplex_configuration_independent(dmg, seed=seed) 53 | rmg = mn.multiplex_configuration_independent(dmg) 54 | -------------------------------------------------------------------------------- /multinet/tests/test_shortest_path.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | 3 | import multinet as mn 4 | import networkx as nx 5 | 6 | g1 = nx.Graph() 7 | dg1 = nx.DiGraph() 8 | nodes = ['A', 'B', 'C', 'D', 'E'] 9 | edges = [('A', 'B'), ('C', 'D')] 10 | g1.add_nodes_from(nodes) 11 | g1.add_edges_from(edges) 12 | dg1.add_nodes_from(nodes) 13 | dg1.add_edges_from(edges) 14 | 15 | g2 = nx.Graph() 16 | dg2 = nx.DiGraph() 17 | nodes = ['A', 'B', 'C', 'D', 'E'] 18 | edges = [('A', 'C'), ('B', 'D')] 19 | g2.add_nodes_from(nodes) 20 | g2.add_edges_from(edges) 21 | dg2.add_nodes_from(nodes) 22 | dg2.add_edges_from(edges) 23 | 24 | g3 = nx.Graph() 25 | dg3 = nx.DiGraph() 26 | nodes = ['A', 'B', 'C', 'D', 'E'] 27 | edges = [('E', 'A'), ('D', 'E')] 28 | g3.add_nodes_from(nodes) 29 | g3.add_edges_from(edges) 30 | dg3.add_nodes_from(nodes) 31 | dg3.add_edges_from(edges) 32 | 33 | mg = mn.Multinet() 34 | mg.add_layer(g1, '1') 35 | mg.add_layer(g2, '2') 36 | mg.add_layer(g3, '3') 37 | 38 | dmg = mn.DiMultinet() 39 | dmg.add_layer(dg1, '1') 40 | dmg.add_layer(dg2, '2') 41 | dmg.add_layer(dg3, '3') 42 | 43 | 44 | class TestShortestPath(object): 45 | 46 | 47 | def test_all_pairs_k_layer_shortest_path_length_ud(self): 48 | all_layer_spl = mn.all_pairs_k_layer_shortest_path_length(mg, 3) 49 | assert len(all_layer_spl) == 20 50 | assert all_layer_spl[('A', 'B')] == 1 51 | assert all_layer_spl[('A', 'D')] == 2 52 | assert all_layer_spl[('A', 'E')] == 1 53 | assert all_layer_spl[('B', 'C')] == 2 54 | 55 | one_layer_spl = mn.all_pairs_k_layer_shortest_path_length(mg, 1) 56 | assert len(all_layer_spl) == 20 57 | assert one_layer_spl[('A', 'B')] == 1 58 | assert one_layer_spl[('A', 'D')] == 2 59 | assert one_layer_spl[('A', 'E')] == 1 60 | assert one_layer_spl[('B', 'C')] == float('inf') 61 | 62 | two_layer_spl = mn.all_pairs_k_layer_shortest_path_length(mg, -1) 63 | assert len(two_layer_spl) == 20 64 | assert two_layer_spl[('A', 'B')] == 1 65 | assert two_layer_spl[('A', 'D')] == 2 66 | assert two_layer_spl[('A', 'E')] == 1 67 | assert two_layer_spl[('B', 'C')] == 2 68 | 69 | 70 | def test_all_pairs_k_layer_shortest_path_length_d(self): 71 | all_layer_spl = mn.all_pairs_k_layer_shortest_path_length(dmg, 3) 72 | assert len(all_layer_spl) == 20 73 | assert all_layer_spl[('A', 'B')] == 1 74 | assert all_layer_spl[('A', 'D')] == 2 75 | assert all_layer_spl[('A', 'E')] == 3 76 | 77 | one_layer_spl = mn.all_pairs_k_layer_shortest_path_length(dmg, 1) 78 | assert len(all_layer_spl) == 20 79 | assert one_layer_spl[('A', 'B')] == 1 80 | assert one_layer_spl[('A', 'D')] == float('inf') 81 | assert one_layer_spl[('A', 'E')] == float('inf') 82 | 83 | two_layer_spl = mn.all_pairs_k_layer_shortest_path_length(dmg, -1) 84 | assert len(two_layer_spl) == 20 85 | assert two_layer_spl[('A', 'B')] == 1 86 | assert two_layer_spl[('A', 'D')] == 2 87 | assert two_layer_spl[('A', 'E')] == float('inf') 88 | 89 | 90 | def test_k_layer_reachability(self): 91 | assert mn.k_layer_reachability(mg, 3) == 1 92 | assert mn.k_layer_reachability(mg, 1) == 0.7 93 | assert mn.k_layer_reachability(mg, -1) == 1 94 | 95 | assert mn.k_layer_reachability(dmg, 3) == 1 96 | assert mn.k_layer_reachability(dmg, 1) == 0.35 97 | assert mn.k_layer_reachability(dmg, -1) == 0.9 98 | 99 | 100 | def test_harmonic_mean_shortest_path(self): 101 | result = mn.harmonic_mean_shortest_path_length(mg) 102 | assert result[('A', 'B')] == 1 103 | assert result[('A', 'D')] == 2 104 | 105 | result = mn.harmonic_mean_shortest_path_length(dmg) 106 | assert result[('A', 'B')] == 1 107 | assert result[('D', 'A')] == 2 108 | assert ('A', 'D') not in result -------------------------------------------------------------------------------- /multinet/util/__init__.py: -------------------------------------------------------------------------------- 1 | from multinet.util.filter_helper import * 2 | from multinet.util.weight_helper import * 3 | from multinet.util.layer_helper import * 4 | -------------------------------------------------------------------------------- /multinet/util/filter_helper.py: -------------------------------------------------------------------------------- 1 | """Utility function to build a filter used by builder. 2 | 3 | filter_func should take two arguments. 4 | The first argument is a dict describe the csv file. 5 | The key of the dict is field name for csv file. 6 | The value of the dict is field index for csv file. 7 | The second argument is a list represent a line in csv file. 8 | filter_func should return a bool. 9 | True if this line is going to be used by the builder. 10 | False if not. 11 | """ 12 | default_filter = lambda x,y:True 13 | 14 | def build_and_filter(**condition): 15 | def filter_func(index_dict,line): 16 | for key in condition: 17 | index = index_dict[key] 18 | if line[index] != condition[key]: 19 | return False 20 | return True 21 | return filter_func 22 | 23 | def build_nand_filter(**condition): 24 | def filter_func(index_dict,line): 25 | for key in condition: 26 | index = index_dict[key] 27 | if line[index] == condition[key]: 28 | return False 29 | return True 30 | return filter_func 31 | 32 | def build_in_and_filter(**condition): 33 | def filter_func(index_dict,line): 34 | for key in condition: 35 | index = index_dict[key] 36 | if not line[index] in condition[key]: 37 | return False 38 | return True 39 | return filter_func 40 | 41 | def combine_filters_and(*filters): 42 | def filt_func(index_dict,line): 43 | ret = True 44 | for filt in filters: 45 | ret = ret and filt(index_dict,line) 46 | return ret 47 | return filt_func 48 | 49 | def combine_filters_or(*filters): 50 | def filt_func(index_dict,line): 51 | ret = False 52 | for filt in filters: 53 | ret = ret or filt(index_dict,line) 54 | return ret 55 | return filt_func 56 | 57 | def combine_filters_not(filt): 58 | return lambda i,l:not filt(i,l) 59 | 60 | def build_either_in_filter(List): 61 | return combine_filters_or(build_in_and_filter(ORIGIN=List),build_in_and_filter(DEST=List)) 62 | 63 | def build_both_in_filter(List): 64 | return build_in_and_filter(ORIGIN=List,DEST=List) 65 | 66 | def regular_filter(): 67 | filt_class = build_and_filter(CLASS='F') 68 | filt_scheduled = build_nand_filter(DEPARTURES_SCHEDULED=0.0) 69 | filt_passenger = build_nand_filter(PASSENGERS=0.0) 70 | return combine_filters_and(filt_class,filt_scheduled,filt_passenger) 71 | 72 | def cargo_filter(): 73 | filt_performed = build_nand_filter(DEPARTURES_PERFORMED=0.0) 74 | return combine_filters_and(filt_performed) -------------------------------------------------------------------------------- /multinet/util/layer_helper.py: -------------------------------------------------------------------------------- 1 | """Utility function to build a layer_func used by builder. 2 | 3 | layer_func should take two arguments. 4 | The first argument is a dict describe the csv file. 5 | The key of the dict is field name for csv file. 6 | The value of the dict is field index for csv file. 7 | The second argument is a list represent a line in csv file. 8 | layer_func should return a str as the layer name. 9 | """ 10 | 11 | default_layer = lambda x,y:"Default_layer" 12 | 13 | def layer_from_string(layer_s): 14 | def layer_func(index_dict,line): 15 | l = index_dict[layer_s] 16 | return line[l] 17 | return layer_func 18 | 19 | def layer_comtrade(aggregation=0): 20 | def layer_func(index_dict,line): 21 | l = index_dict['hs6'] 22 | code = line[l] 23 | return str(int(code/(10**aggregation))) 24 | return layer_func 25 | 26 | def layer_icews(dlevel=2): 27 | def layer_func(index_dict,line): 28 | l = index_dict['CAMEO Code'] 29 | code = line[l] 30 | return code[:dlevel] 31 | return layer_func -------------------------------------------------------------------------------- /multinet/util/weight_helper.py: -------------------------------------------------------------------------------- 1 | """Utility function to build a weight_func used by builder. 2 | 3 | weight_func should take two arguments. 4 | The first argument is a dict describe the csv file. 5 | The key of the dict is field name for csv file. 6 | The value of the dict is field index for csv file. 7 | The second argument is a list represent a line in csv file. 8 | weight_func should return a number as the weight of the edge. 9 | """ 10 | default_weight = lambda x,y:1.0 11 | 12 | def weight_from_string(weight_s): 13 | def weight_func(index_dict,line): 14 | w = index_dict[weight_s] 15 | return float(line[w]) 16 | return weight_func 17 | 18 | def weight_from_ratio(ref_g,weight_s): 19 | def weight_func(index_dict,line): 20 | w = index_dict[weight_s] 21 | origin = index_dict['ORIGIN'] 22 | dest = index_dict['DEST'] 23 | source = line[origin] 24 | target = line[dest] 25 | try: 26 | ref_w = ref_g[source][target]['weight'] 27 | except: 28 | ref_w = 0 29 | if ref_w != 0: 30 | return float(line[w])/ref_w 31 | else: 32 | return 0 33 | return weight_func 34 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup 4 | 5 | packages = ['multinet', 'multinet.util', 'multinet.tests'] 6 | package_dir = {'multinet': 'multinet', 7 | 'multinet.util': 'multinet/util', 8 | 'multinet.tests': 'multinet/tests'} 9 | 10 | if __name__ == '__main__': 11 | 12 | setup(name='multinet', 13 | version='0.01', 14 | description = 'Networkx extension for multiplex networks', 15 | author='Haochen Wu', 16 | author_email='multinet@haochenwu.com', 17 | packages=packages, 18 | package_dir=package_dir, 19 | install_requires=['networkx','dit'] 20 | ) 21 | -------------------------------------------------------------------------------- /tools/travis/linux_install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | pip install --upgrade pytest pytest-cov codecov 6 | 7 | if [[ "${REPORT_COVERAGE}" == 1 ]]; then 8 | pip install -e .; 9 | else 10 | pip install .; 11 | fi 12 | 13 | set +e 14 | -------------------------------------------------------------------------------- /tools/travis/script.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | export MN_SOURCE=$PWD 6 | export MN_INSTALL=`pip show multinet | grep Location | awk '{print $2"/multinet"}'` 7 | 8 | if [[ "${REPORT_COVERAGE}" == 1 ]]; then 9 | pytest --cov 10 | else 11 | cd $MN_INSTALL 12 | printenv PWD 13 | pytest 14 | cd $MN_SOURCE 15 | fi 16 | 17 | set +e 18 | --------------------------------------------------------------------------------