| 1 | #! /usr/bin/env python |
|---|
| 2 | # -*- coding: utf-8 -*- |
|---|
| 3 | # |
|---|
| 4 | # graph_tool -- a general graph manipulation python module |
|---|
| 5 | # |
|---|
| 6 | # Copyright (C) 2007-2011 Tiago de Paula Peixoto <tiago@skewed.de> |
|---|
| 7 | # |
|---|
| 8 | # This program is free software: you can redistribute it and/or modify |
|---|
| 9 | # it under the terms of the GNU General Public License as published by |
|---|
| 10 | # the Free Software Foundation, either version 3 of the License, or |
|---|
| 11 | # (at your option) any later version. |
|---|
| 12 | # |
|---|
| 13 | # This program is distributed in the hope that it will be useful, |
|---|
| 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 16 | # GNU General Public License for more details. |
|---|
| 17 | # |
|---|
| 18 | # You should have received a copy of the GNU General Public License |
|---|
| 19 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|---|
| 20 | |
|---|
| 21 | """ |
|---|
| 22 | graph_tool - efficient graph analysis and manipulation |
|---|
| 23 | ====================================================== |
|---|
| 24 | |
|---|
| 25 | Summary |
|---|
| 26 | ------- |
|---|
| 27 | |
|---|
| 28 | .. autosummary:: |
|---|
| 29 | :nosignatures: |
|---|
| 30 | |
|---|
| 31 | Graph |
|---|
| 32 | GraphView |
|---|
| 33 | Vertex |
|---|
| 34 | Edge |
|---|
| 35 | PropertyMap |
|---|
| 36 | PropertyArray |
|---|
| 37 | load_graph |
|---|
| 38 | group_vector_property |
|---|
| 39 | ungroup_vector_property |
|---|
| 40 | value_types |
|---|
| 41 | show_config |
|---|
| 42 | |
|---|
| 43 | |
|---|
| 44 | This module provides: |
|---|
| 45 | |
|---|
| 46 | 1. A :class:`~graph_tool.Graph` class for graph representation and manipulation |
|---|
| 47 | 2. Property maps for Vertex, Edge or Graph. |
|---|
| 48 | 3. Fast algorithms implemented in C++. |
|---|
| 49 | |
|---|
| 50 | How to use the documentation |
|---|
| 51 | ---------------------------- |
|---|
| 52 | |
|---|
| 53 | Documentation is available in two forms: docstrings provided |
|---|
| 54 | with the code, and the full documentation available in |
|---|
| 55 | `the graph-tool homepage <http://graph-tool.skewed.de>`_. |
|---|
| 56 | |
|---|
| 57 | We recommend exploring the docstrings using `IPython |
|---|
| 58 | <http://ipython.scipy.org>`_, an advanced Python shell with TAB-completion and |
|---|
| 59 | introspection capabilities. |
|---|
| 60 | |
|---|
| 61 | The docstring examples assume that ``graph_tool.all`` has been imported as |
|---|
| 62 | ``gt``:: |
|---|
| 63 | |
|---|
| 64 | >>> import graph_tool.all as gt |
|---|
| 65 | |
|---|
| 66 | Code snippets are indicated by three greater-than signs:: |
|---|
| 67 | |
|---|
| 68 | >>> x = x + 1 |
|---|
| 69 | |
|---|
| 70 | Use the built-in ``help`` function to view a function's docstring:: |
|---|
| 71 | |
|---|
| 72 | >>> help(gt.Graph) |
|---|
| 73 | |
|---|
| 74 | Contents |
|---|
| 75 | -------- |
|---|
| 76 | """ |
|---|
| 77 | |
|---|
| 78 | __author__ = "Tiago de Paula Peixoto <tiago@skewed.de>" |
|---|
| 79 | __copyright__ = "Copyright 2007-2011 Tiago de Paula Peixoto" |
|---|
| 80 | __license__ = "GPL version 3 or above" |
|---|
| 81 | __URL__ = "http://graph-tool.skewed.de" |
|---|
| 82 | |
|---|
| 83 | # import numpy and scipy before everything to avoid weird segmentation faults |
|---|
| 84 | # depending on the order things are imported. |
|---|
| 85 | |
|---|
| 86 | import numpy |
|---|
| 87 | import numpy.ma |
|---|
| 88 | import scipy |
|---|
| 89 | import scipy.stats |
|---|
| 90 | |
|---|
| 91 | from dl_import import * |
|---|
| 92 | dl_import("import libgraph_tool_core as libcore") |
|---|
| 93 | import libgraph_tool_core as libcore # for pylint |
|---|
| 94 | __version__ = libcore.mod_info().version |
|---|
| 95 | |
|---|
| 96 | import io # sets up libcore io routines |
|---|
| 97 | |
|---|
| 98 | import sys |
|---|
| 99 | import os |
|---|
| 100 | import re |
|---|
| 101 | import gzip |
|---|
| 102 | import bz2 |
|---|
| 103 | import weakref |
|---|
| 104 | import copy |
|---|
| 105 | |
|---|
| 106 | from StringIO import StringIO |
|---|
| 107 | from decorators import _wraps, _require, _attrs, _limit_args |
|---|
| 108 | from inspect import ismethod |
|---|
| 109 | |
|---|
| 110 | __all__ = ["Graph", "GraphView", "Vertex", "Edge", "Vector_bool", |
|---|
| 111 | "Vector_int32_t", "Vector_int64_t", "Vector_double", |
|---|
| 112 | "Vector_long_double", "Vector_string", "value_types", "load_graph", |
|---|
| 113 | "PropertyMap", "group_vector_property", "ungroup_vector_property", |
|---|
| 114 | "show_config", "PropertyArray", "__author__", "__copyright__", |
|---|
| 115 | "__URL__", "__version__"] |
|---|
| 116 | |
|---|
| 117 | # this is rather pointless, but it works around a sphinx bug |
|---|
| 118 | graph_tool = sys.modules[__name__] |
|---|
| 119 | |
|---|
| 120 | ################################################################################ |
|---|
| 121 | # Utility functions |
|---|
| 122 | ################################################################################ |
|---|
| 123 | |
|---|
| 124 | |
|---|
| 125 | def _prop(t, g, prop): |
|---|
| 126 | """Return either a property map, or an internal property map with a given |
|---|
| 127 | name.""" |
|---|
| 128 | if type(prop) == str: |
|---|
| 129 | try: |
|---|
| 130 | pmap = g.properties[(t, prop)] |
|---|
| 131 | except KeyError: |
|---|
| 132 | raise KeyError("no internal %s property named: %s" %\ |
|---|
| 133 | ("vertex" if t == "v" else \ |
|---|
| 134 | ("edge" if t == "e" else "graph"), prop)) |
|---|
| 135 | else: |
|---|
| 136 | pmap = prop |
|---|
| 137 | if pmap == None: |
|---|
| 138 | return libcore.any() |
|---|
| 139 | else: |
|---|
| 140 | if t != prop.key_type(): |
|---|
| 141 | names = {'e': 'edge', 'v': 'vertex', 'g': 'graph'} |
|---|
| 142 | raise ValueError("Expected '%s' property map, got '%s'" % |
|---|
| 143 | (names[t], names[prop.key_type()])) |
|---|
| 144 | return pmap._PropertyMap__map.get_map() |
|---|
| 145 | |
|---|
| 146 | |
|---|
| 147 | def _degree(g, name): |
|---|
| 148 | """Retrieve the degree type from string, or returns the corresponding |
|---|
| 149 | property map.""" |
|---|
| 150 | deg = name |
|---|
| 151 | if name == "in-degree" or name == "in": |
|---|
| 152 | deg = libcore.Degree.In |
|---|
| 153 | elif name == "out-degree" or name == "out": |
|---|
| 154 | deg = libcore.Degree.Out |
|---|
| 155 | elif name == "total-degree" or name == "total": |
|---|
| 156 | deg = libcore.Degree.Total |
|---|
| 157 | else: |
|---|
| 158 | deg = _prop("v", g, deg) |
|---|
| 159 | return deg |
|---|
| 160 | |
|---|
| 161 | |
|---|
| 162 | def _type_alias(type_name): |
|---|
| 163 | alias = {"int8_t": "bool", |
|---|
| 164 | "boolean": "bool", |
|---|
| 165 | "int": "int32_t", |
|---|
| 166 | "long": "int64_t", |
|---|
| 167 | "long long": "int64_t", |
|---|
| 168 | "object": "python::object", |
|---|
| 169 | "float": "double"} |
|---|
| 170 | if type_name in value_types(): |
|---|
| 171 | return type_name |
|---|
| 172 | if type_name in alias: |
|---|
| 173 | return alias[type_name] |
|---|
| 174 | ma = re.compile(r"vector<(.*)>").match(type_name) |
|---|
| 175 | if ma: |
|---|
| 176 | t = ma.group(1) |
|---|
| 177 | if t in alias: |
|---|
| 178 | return "vector<%s>" % alias[t] |
|---|
| 179 | raise ValueError("invalid property value type: " + type_name) |
|---|
| 180 | |
|---|
| 181 | |
|---|
| 182 | def _python_type(type_name): |
|---|
| 183 | type_name = _type_alias(type_name) |
|---|
| 184 | if "vector" in type_name: |
|---|
| 185 | ma = re.compile(r"vector<(.*)>").match(type_name) |
|---|
| 186 | t = ma.group(1) |
|---|
| 187 | return list, _python_type(t) |
|---|
| 188 | if "int" in type_name: |
|---|
| 189 | return int |
|---|
| 190 | if type_name == "bool": |
|---|
| 191 | return bool |
|---|
| 192 | if "double" in type_name: |
|---|
| 193 | return float |
|---|
| 194 | if "string" in type_name: |
|---|
| 195 | return str |
|---|
| 196 | return object |
|---|
| 197 | |
|---|
| 198 | |
|---|
| 199 | def _convert(prop, val): |
|---|
| 200 | # attempt to convert to a compatible python type. This is useful, |
|---|
| 201 | # for instance, when dealing with numpy types. |
|---|
| 202 | vtype = _python_type(prop.value_type()) |
|---|
| 203 | if type(vtype) is tuple: |
|---|
| 204 | return [vtype[1](x) for x in val] |
|---|
| 205 | return vtype(val) |
|---|
| 206 | |
|---|
| 207 | |
|---|
| 208 | def show_config(): |
|---|
| 209 | """Show ``graph_tool`` build configuration.""" |
|---|
| 210 | info = libcore.mod_info() |
|---|
| 211 | print "version:", info.version |
|---|
| 212 | print "gcc version:", info.gcc_version |
|---|
| 213 | print "compilation flags:", info.cxxflags |
|---|
| 214 | print "install prefix:", info.install_prefix |
|---|
| 215 | print "python dir:", info.python_dir |
|---|
| 216 | print "graph filtering:", libcore.graph_filtering_enabled() |
|---|
| 217 | print "openmp:", libcore.openmp_enabled() |
|---|
| 218 | print "uname:", " ".join(os.uname()) |
|---|
| 219 | |
|---|
| 220 | ################################################################################ |
|---|
| 221 | # Property Maps |
|---|
| 222 | ################################################################################ |
|---|
| 223 | |
|---|
| 224 | |
|---|
| 225 | class PropertyArray(numpy.ndarray): |
|---|
| 226 | """This is a :class:`~numpy.ndarray` subclass which keeps a reference of its :class:`~graph_tool.PropertyMap` owner, and detects if the underlying data has been invalidated.""" |
|---|
| 227 | |
|---|
| 228 | __array_priority__ = -10 |
|---|
| 229 | |
|---|
| 230 | def _get_pmap(self): |
|---|
| 231 | return self._prop_map |
|---|
| 232 | |
|---|
| 233 | def _set_pmap(self, value): |
|---|
| 234 | self._prop_map = value |
|---|
| 235 | |
|---|
| 236 | prop_map = property(_get_pmap, _set_pmap, |
|---|
| 237 | doc=":class:`~graph_tool.PropertyMap` owner instance.") |
|---|
| 238 | |
|---|
| 239 | def __new__(cls, input_array, prop_map): |
|---|
| 240 | obj = numpy.asarray(input_array).view(cls) |
|---|
| 241 | obj.prop_map = prop_map |
|---|
| 242 | |
|---|
| 243 | # check if data really belongs to property map |
|---|
| 244 | if (prop_map._get_data().__array_interface__['data'][0] != |
|---|
| 245 | obj._get_base_data()): |
|---|
| 246 | obj.prop_map = None |
|---|
| 247 | # do a copy |
|---|
| 248 | obj = numpy.asarray(obj) |
|---|
| 249 | |
|---|
| 250 | return obj |
|---|
| 251 | |
|---|
| 252 | def _get_base(self): |
|---|
| 253 | base = self |
|---|
| 254 | while base.base is not None: |
|---|
| 255 | base = base.base |
|---|
| 256 | return base |
|---|
| 257 | |
|---|
| 258 | def _get_base_data(self): |
|---|
| 259 | return self._get_base().__array_interface__['data'][0] |
|---|
| 260 | |
|---|
| 261 | def _check_data(self): |
|---|
| 262 | if self.prop_map is None: |
|---|
| 263 | return |
|---|
| 264 | |
|---|
| 265 | data = self.prop_map._get_data() |
|---|
| 266 | |
|---|
| 267 | if (data is None or |
|---|
| 268 | data.__array_interface__['data'][0] != self._get_base_data()): |
|---|
| 269 | raise ValueError(("The graph correspondig to the underlying" + |
|---|
| 270 | " property map %s has changed. The" + |
|---|
| 271 | " PropertyArray at 0x%x is no longer valid!") % |
|---|
| 272 | (repr(self.prop_map), id(self))) |
|---|
| 273 | |
|---|
| 274 | def __array_finalize__(self, obj): |
|---|
| 275 | if type(obj) is PropertyArray: |
|---|
| 276 | obj._check_data() |
|---|
| 277 | |
|---|
| 278 | if obj is not None: |
|---|
| 279 | # inherit prop_map only if the data is the same |
|---|
| 280 | if (type(obj) is PropertyArray and |
|---|
| 281 | self._get_base_data() == obj._get_base_data()): |
|---|
| 282 | self.prop_map = getattr(obj, 'prop_map', None) |
|---|
| 283 | else: |
|---|
| 284 | self.prop_map = None |
|---|
| 285 | self._check_data() |
|---|
| 286 | |
|---|
| 287 | def __array_prepare__(self, out_arr, context=None): |
|---|
| 288 | self._check_data() |
|---|
| 289 | return numpy.ndarray.__array_prepare__(self, out_arr, context) |
|---|
| 290 | |
|---|
| 291 | def __array_wrap__(self, out_arr, context=None): |
|---|
| 292 | #demote to ndarray |
|---|
| 293 | obj = numpy.ndarray.__array_wrap__(self, out_arr, context) |
|---|
| 294 | return numpy.asarray(obj) |
|---|
| 295 | |
|---|
| 296 | # Overload members and operators to add data checking |
|---|
| 297 | |
|---|
| 298 | def _wrap_method(method): |
|---|
| 299 | method = getattr(numpy.ndarray, method) |
|---|
| 300 | |
|---|
| 301 | def checked_method(self, *args, **kwargs): |
|---|
| 302 | self._check_data() |
|---|
| 303 | return method(self, *args, **kwargs) |
|---|
| 304 | |
|---|
| 305 | if ismethod(method): |
|---|
| 306 | checked_method = _wraps(method)(checked_method) |
|---|
| 307 | checked_method.__doc__ = getattr(method, "__doc__", None) |
|---|
| 308 | return checked_method |
|---|
| 309 | |
|---|
| 310 | for method in ['all', 'any', 'argmax', 'argmin', 'argsort', 'astype', |
|---|
| 311 | 'byteswap', 'choose', 'clip', 'compress', 'conj', |
|---|
| 312 | 'conjugate', 'copy', 'cumprod', 'cumsum', 'diagonal', 'dot', |
|---|
| 313 | 'dump', 'dumps', 'fill', 'flat', 'flatten', 'getfield', |
|---|
| 314 | 'imag', 'item', 'itemset', 'itemsize', 'max', 'mean', 'min', |
|---|
| 315 | 'newbyteorder', 'nonzero', 'prod', 'ptp', 'put', 'ravel', |
|---|
| 316 | 'real', 'repeat', 'reshape', 'resize', 'round', |
|---|
| 317 | 'searchsorted', 'setfield', 'setflags', 'sort', 'squeeze', |
|---|
| 318 | 'std', 'sum', 'swapaxes', 'take', 'tofile', 'tolist', |
|---|
| 319 | 'tostring', 'trace', 'transpose', 'var', 'view', |
|---|
| 320 | '__getitem__']: |
|---|
| 321 | if hasattr(numpy.ndarray, method): |
|---|
| 322 | locals()[method] = _wrap_method(method) |
|---|
| 323 | |
|---|
| 324 | |
|---|
| 325 | class PropertyMap(object): |
|---|
| 326 | """This class provides a mapping from vertices, edges or whole graphs to arbitrary properties. |
|---|
| 327 | |
|---|
| 328 | See :ref:`sec_property_maps` for more details. |
|---|
| 329 | |
|---|
| 330 | The possible property value types are listed below. |
|---|
| 331 | |
|---|
| 332 | .. table:: |
|---|
| 333 | |
|---|
| 334 | ======================= ====================== |
|---|
| 335 | Type name Alias |
|---|
| 336 | ======================= ====================== |
|---|
| 337 | ``bool`` ``uint8_t`` |
|---|
| 338 | ``int32_t`` ``int`` |
|---|
| 339 | ``int64_t`` ``long``, ``long long`` |
|---|
| 340 | ``double`` ``float`` |
|---|
| 341 | ``long double`` |
|---|
| 342 | ``string`` |
|---|
| 343 | ``vector<bool>`` ``vector<uint8_t>`` |
|---|
| 344 | ``vector<int32_t>`` ``vector<int>`` |
|---|
| 345 | ``vector<int64_t>`` ``vector<long>``, ``vector<long long>`` |
|---|
| 346 | ``vector<double>`` ``vector<float>`` |
|---|
| 347 | ``vector<long double>`` |
|---|
| 348 | ``vector<string>`` |
|---|
| 349 | ``python::object`` ``object`` |
|---|
| 350 | ======================= ====================== |
|---|
| 351 | """ |
|---|
| 352 | def __init__(self, pmap, g, key_type, key_trans=None): |
|---|
| 353 | self.__map = pmap |
|---|
| 354 | self.__g = weakref.ref(g) |
|---|
| 355 | self.__base_g = lambda: None |
|---|
| 356 | try: |
|---|
| 357 | if isinstance(g, GraphView): |
|---|
| 358 | self.__base_g = weakref.ref(g.base) # keep reference to the |
|---|
| 359 | # base graph, in case the |
|---|
| 360 | # graph view is deleted. |
|---|
| 361 | except NameError: |
|---|
| 362 | pass # ignore if GraphView is yet undefined |
|---|
| 363 | self.__key_type = key_type |
|---|
| 364 | self.__key_trans = key_trans if key_trans is not None else lambda k: k |
|---|
| 365 | self.__register_map() |
|---|
| 366 | |
|---|
| 367 | def __register_map(self): |
|---|
| 368 | for g in [self.__g(), self.__base_g()]: |
|---|
| 369 | if g is not None: |
|---|
| 370 | g._Graph__known_properties.append((self.key_type(), |
|---|
| 371 | weakref.ref(self.__map))) |
|---|
| 372 | |
|---|
| 373 | def __unregister_map(self): |
|---|
| 374 | for g in [self.__g(), self.__base_g()]: |
|---|
| 375 | if g is not None: |
|---|
| 376 | i = g._Graph__known_properties.index((self.key_type(), |
|---|
| 377 | weakref.ref(self.__map))) |
|---|
| 378 | del g._Graph__known_properties[i] |
|---|
| 379 | |
|---|
| 380 | def __del__(self): |
|---|
| 381 | self.__unregister_map() |
|---|
| 382 | |
|---|
| 383 | def __getitem__(self, k): |
|---|
| 384 | return self.__map[self.__key_trans(k)] |
|---|
| 385 | |
|---|
| 386 | def __setitem__(self, k, v): |
|---|
| 387 | key = self.__key_trans(k) |
|---|
| 388 | try: |
|---|
| 389 | self.__map[key] = v |
|---|
| 390 | except TypeError: |
|---|
| 391 | self.__map[key] = _convert(self, v) |
|---|
| 392 | |
|---|
| 393 | def __repr__(self): |
|---|
| 394 | # provide some more useful information |
|---|
| 395 | if self.key_type() == "e": |
|---|
| 396 | k = "Edge" |
|---|
| 397 | elif self.key_type() == "v": |
|---|
| 398 | k = "Vertex" |
|---|
| 399 | else: |
|---|
| 400 | k = "Graph" |
|---|
| 401 | g = self.get_graph() |
|---|
| 402 | if g == None: |
|---|
| 403 | g = "a non-existent graph" |
|---|
| 404 | else: |
|---|
| 405 | g = "Graph 0x%x" % id(g) |
|---|
| 406 | return ("<PropertyMap object with key type '%s' and value type '%s'," |
|---|
| 407 | + " for %s, at 0x%x>") % (k, self.value_type(), g, id(self)) |
|---|
| 408 | |
|---|
| 409 | def get_graph(self): |
|---|
| 410 | """Get the graph class to which the map refers.""" |
|---|
| 411 | g = self.__g() |
|---|
| 412 | if g is None: |
|---|
| 413 | g = self.__base_g() |
|---|
| 414 | return g |
|---|
| 415 | |
|---|
| 416 | def key_type(self): |
|---|
| 417 | """Return the key type of the map. Either 'g', 'v' or 'e'.""" |
|---|
| 418 | return self.__key_type |
|---|
| 419 | |
|---|
| 420 | def value_type(self): |
|---|
| 421 | """Return the value type of the map.""" |
|---|
| 422 | return self.__map.value_type() |
|---|
| 423 | |
|---|
| 424 | def python_value_type(self): |
|---|
| 425 | """Return the python-compatible value type of the map.""" |
|---|
| 426 | return _python_type(self.__map.value_type()) |
|---|
| 427 | |
|---|
| 428 | def get_array(self): |
|---|
| 429 | """Get a :class:`~graph_tool.PropertyArray` with the property values. |
|---|
| 430 | |
|---|
| 431 | .. note:: |
|---|
| 432 | |
|---|
| 433 | An array is returned *only if* the value type of the property map is |
|---|
| 434 | a scalar. For vector, string or object types, ``None`` is returned |
|---|
| 435 | instead. |
|---|
| 436 | |
|---|
| 437 | .. warning:: |
|---|
| 438 | |
|---|
| 439 | The returned array does not own the data, which belongs to the |
|---|
| 440 | property map. Therefore, if the graph changes, the array may become |
|---|
| 441 | *invalid* and any operation on it will fail with a |
|---|
| 442 | :class:`ValueError` exception. Do **not** store the array if |
|---|
| 443 | the graph is to be modified; store a **copy** instead. |
|---|
| 444 | """ |
|---|
| 445 | a = self._get_data() |
|---|
| 446 | if a is None: |
|---|
| 447 | return None |
|---|
| 448 | return PropertyArray(a, prop_map=self) |
|---|
| 449 | |
|---|
| 450 | def _get_data(self): |
|---|
| 451 | g = self.get_graph() |
|---|
| 452 | if g is None: |
|---|
| 453 | return None |
|---|
| 454 | g.stash_filter(edge=True, vertex=True) |
|---|
| 455 | if self.__key_type == 'v': |
|---|
| 456 | n = g.num_vertices() |
|---|
| 457 | elif self.__key_type == 'e': |
|---|
| 458 | n = g._Graph__graph.GetMaxEdgeIndex() + 1 |
|---|
| 459 | else: |
|---|
| 460 | n = 1 |
|---|
| 461 | g.pop_filter(edge=True, vertex=True) |
|---|
| 462 | a = self.__map.get_array(n) |
|---|
| 463 | return a |
|---|
| 464 | |
|---|
| 465 | def __set_array(self, v): |
|---|
| 466 | a = self.get_array() |
|---|
| 467 | if a is None: |
|---|
| 468 | return |
|---|
| 469 | a[:] = v |
|---|
| 470 | |
|---|
| 471 | a = property(get_array, __set_array, |
|---|
| 472 | doc=r"""Shortcut to the :meth:`~PropertyMap.get_array` method |
|---|
| 473 | as an attribute. This makes assignments more convenient, e.g.: |
|---|
| 474 | |
|---|
| 475 | >>> g = gt.Graph() |
|---|
| 476 | >>> g.add_vertex(10) |
|---|
| 477 | [...] |
|---|
| 478 | >>> prop = g.new_vertex_property("double") |
|---|
| 479 | >>> prop.a = np.random.random(10) # Assignment from array |
|---|
| 480 | """) |
|---|
| 481 | |
|---|
| 482 | def __get_set_f_array(self, v=None, get=True): |
|---|
| 483 | g = self.get_graph() |
|---|
| 484 | if g is None: |
|---|
| 485 | return None |
|---|
| 486 | a = self.get_array() |
|---|
| 487 | filt = [None] |
|---|
| 488 | if self.__key_type == 'v': |
|---|
| 489 | filt = g.get_vertex_filter() |
|---|
| 490 | elif self.__key_type == 'e': |
|---|
| 491 | filt = g.get_edge_filter() |
|---|
| 492 | if filt[0] is None or a is None: |
|---|
| 493 | if get: |
|---|
| 494 | return a |
|---|
| 495 | else: |
|---|
| 496 | return |
|---|
| 497 | if get: |
|---|
| 498 | return a[filt[0].a == (not filt[1])] |
|---|
| 499 | else: |
|---|
| 500 | a[filt[0].a == (not filt[1])] = v |
|---|
| 501 | |
|---|
| 502 | fa = property(__get_set_f_array, |
|---|
| 503 | lambda self, v: self.__get_set_f_array(v, False), |
|---|
| 504 | doc=r"""The same as the :attr:`~PropertyMap.a` attribute, but |
|---|
| 505 | instead an *indexed* array is returned, which contains only |
|---|
| 506 | entries for vertices/edges which are not filtered out. If |
|---|
| 507 | there are no filters in place, the array is not indexed, and |
|---|
| 508 | is identical to the :attr:`~PropertyMap.a` attribute. |
|---|
| 509 | |
|---|
| 510 | Note that because advanced indexing is triggered, a **copy** |
|---|
| 511 | of the array is returned, not a view, as for the |
|---|
| 512 | :attr:`~PropertyMap.a` attribute. Nevertheless, the assignment |
|---|
| 513 | of values to the *whole* array at once works as expected.""") |
|---|
| 514 | |
|---|
| 515 | def __get_set_m_array(self, v=None, get=True): |
|---|
| 516 | g = self.get_graph() |
|---|
| 517 | if g is None: |
|---|
| 518 | return None |
|---|
| 519 | a = self.get_array() |
|---|
| 520 | filt = [None] |
|---|
| 521 | if self.__key_type == 'v': |
|---|
| 522 | filt = g.get_vertex_filter() |
|---|
| 523 | elif self.__key_type == 'e': |
|---|
| 524 | filt = g.get_edge_filter() |
|---|
| 525 | if filt[0] is None or a is None: |
|---|
| 526 | if get: |
|---|
| 527 | return a |
|---|
| 528 | else: |
|---|
| 529 | return |
|---|
| 530 | ma = numpy.ma.array(a, mask=(filt[0].a == False) if not filt[1] else (filt[0].a == True)) |
|---|
| 531 | if get: |
|---|
| 532 | return ma |
|---|
| 533 | else: |
|---|
| 534 | ma[:] = v |
|---|
| 535 | |
|---|
| 536 | ma = property(__get_set_m_array, |
|---|
| 537 | lambda self, v: self.__get_set_m_array(v, False), |
|---|
| 538 | doc=r"""The same as the :attr:`~PropertyMap.a` attribute, but |
|---|
| 539 | instead a :class:`~numpy.ma.MaskedArray` object is returned, |
|---|
| 540 | which contains only entries for vertices/edges which are not |
|---|
| 541 | filtered out. If there are no filters in place, a regular |
|---|
| 542 | :class:`~graph_tool.PropertyArray` is returned, which is |
|---|
| 543 | identical to the :attr:`~PropertyMap.a` attribute.""") |
|---|
| 544 | |
|---|
| 545 | def is_writable(self): |
|---|
| 546 | """Return True if the property is writable.""" |
|---|
| 547 | return self.__map.is_writable() |
|---|
| 548 | |
|---|
| 549 | |
|---|
| 550 | def _check_prop_writable(prop, name=None): |
|---|
| 551 | if not prop.is_writable(): |
|---|
| 552 | raise ValueError("property map%s is not writable." %\ |
|---|
| 553 | ((" '%s'" % name) if name != None else "")) |
|---|
| 554 | |
|---|
| 555 | |
|---|
| 556 | def _check_prop_scalar(prop, name=None, floating=False): |
|---|
| 557 | scalars = ["bool", "int32_t", "int64_t", "unsigned long", |
|---|
| 558 | "double", "long double"] |
|---|
| 559 | if floating: |
|---|
| 560 | scalars = ["double", "long double"] |
|---|
| 561 | |
|---|
| 562 | if prop.value_type() not in scalars: |
|---|
| 563 | raise ValueError("property map%s is not of scalar%s type." %\ |
|---|
| 564 | (((" '%s'" % name) if name != None else ""), |
|---|
| 565 | (" floating" if floating else ""))) |
|---|
| 566 | |
|---|
| 567 | |
|---|
| 568 | def _check_prop_vector(prop, name=None, scalar=True, floating=False): |
|---|
| 569 | scalars = ["bool", "int32_t", "int64_t", "unsigned long", |
|---|
| 570 | "double", "long double"] |
|---|
| 571 | if not scalar: |
|---|
| 572 | scalars += ["string"] |
|---|
| 573 | if floating: |
|---|
| 574 | scalars = ["double", "long double"] |
|---|
| 575 | vals = ["vector<%s>" % v for v in scalars] |
|---|
| 576 | if prop.value_type() not in vals: |
|---|
| 577 | raise ValueError("property map%s is not of vector%s type." %\ |
|---|
| 578 | (((" '%s'" % name) if name != None else ""), |
|---|
| 579 | (" floating" if floating else ""))) |
|---|
| 580 | |
|---|
| 581 | |
|---|
| 582 | def group_vector_property(props, value_type=None, vprop=None, pos=None): |
|---|
| 583 | """Group list of properties ``props`` into a vector property map of the same type. |
|---|
| 584 | |
|---|
| 585 | Parameters |
|---|
| 586 | ---------- |
|---|
| 587 | props : list of :class:`~graph_tool.PropertyMap` |
|---|
| 588 | Properties to be grouped. |
|---|
| 589 | value_type : string (optional, default: None) |
|---|
| 590 | If supplied, defines the value type of the grouped property. |
|---|
| 591 | vprop : :class:`~graph_tool.PropertyMap` (optional, default: None) |
|---|
| 592 | If supplied, the properties are grouped into this property map. |
|---|
| 593 | pos : list of ints (optional, default: None) |
|---|
| 594 | If supplied, should contain a list of indexes where each corresponding |
|---|
| 595 | element of ``props`` should be inserted. |
|---|
| 596 | |
|---|
| 597 | Returns |
|---|
| 598 | ------- |
|---|
| 599 | vprop : :class:`~graph_tool.PropertyMap` |
|---|
| 600 | A vector property map with the grouped values of each property map in |
|---|
| 601 | ``props``. |
|---|
| 602 | |
|---|
| 603 | Examples |
|---|
| 604 | -------- |
|---|
| 605 | >>> from numpy.random import seed, randint |
|---|
| 606 | >>> from numpy import array |
|---|
| 607 | >>> seed(42) |
|---|
| 608 | >>> g = gt.random_graph(100, lambda: (3, 3)) |
|---|
| 609 | >>> props = [g.new_vertex_property("int") for i in xrange(3)] |
|---|
| 610 | >>> for i in xrange(3): |
|---|
| 611 | ... props[i].a = randint(0, 100, g.num_vertices()) |
|---|
| 612 | >>> gprop = gt.group_vector_property(props) |
|---|
| 613 | >>> print gprop[g.vertex(0)].a |
|---|
| 614 | [71 40 96] |
|---|
| 615 | >>> print array([p[g.vertex(0)] for p in props]) |
|---|
| 616 | [71 40 96] |
|---|
| 617 | """ |
|---|
| 618 | g = props[0].get_graph() |
|---|
| 619 | vtypes = set() |
|---|
| 620 | keys = set() |
|---|
| 621 | for i, p in enumerate(props): |
|---|
| 622 | if "vector" in p.value_type(): |
|---|
| 623 | raise ValueError("property map 'props[%d]' is a vector property." % |
|---|
| 624 | i) |
|---|
| 625 | vtypes.add(p.value_type()) |
|---|
| 626 | keys.add(p.key_type()) |
|---|
| 627 | if len(keys) > 1: |
|---|
| 628 | raise ValueError("'props' must be of the same key type.") |
|---|
| 629 | k = keys.pop() |
|---|
| 630 | |
|---|
| 631 | if vprop == None: |
|---|
| 632 | if value_type == None and len(vtypes) == 1: |
|---|
| 633 | value_type = vtypes.pop() |
|---|
| 634 | |
|---|
| 635 | if value_type != None: |
|---|
| 636 | value_type = "vector<%s>" % value_type |
|---|
| 637 | if k == 'v': |
|---|
| 638 | vprop = g.new_vertex_property(value_type) |
|---|
| 639 | elif k == 'e': |
|---|
| 640 | vprop = g.new_edge_property(value_type) |
|---|
| 641 | else: |
|---|
| 642 | vprop = g.new_graph_property(value_type) |
|---|
| 643 | else: |
|---|
| 644 | ValueError("Can't automatically determine property map value" + |
|---|
| 645 | " type. Please provide the 'value_type' parameter.") |
|---|
| 646 | _check_prop_vector(vprop, name="vprop", scalar=False) |
|---|
| 647 | |
|---|
| 648 | for i, p in enumerate(props): |
|---|
| 649 | if k != "g": |
|---|
| 650 | g.stash_filter(directed=True, reversed=True) |
|---|
| 651 | g.set_directed(True) |
|---|
| 652 | g.set_reversed(False) |
|---|
| 653 | libcore.group_vector_property(g._Graph__graph, _prop(k, g, vprop), |
|---|
| 654 | _prop(k, g, p), |
|---|
| 655 | i if pos == None else pos[i], |
|---|
| 656 | k == 'e') |
|---|
| 657 | g.pop_filter(directed=True, reversed=True) |
|---|
| 658 | else: |
|---|
| 659 | vprop[g][i if pos is None else pos[i]] = p[g] |
|---|
| 660 | return vprop |
|---|
| 661 | |
|---|
| 662 | |
|---|
| 663 | def ungroup_vector_property(vprop, pos, props=None): |
|---|
| 664 | """Ungroup vector property map ``vprop`` into a list of individual property maps. |
|---|
| 665 | |
|---|
| 666 | Parameters |
|---|
| 667 | ---------- |
|---|
| 668 | vprop : :class:`~graph_tool.PropertyMap` |
|---|
| 669 | Vector property map to be ungrouped. |
|---|
| 670 | pos : list of ints |
|---|
| 671 | A list of indexes corresponding to where each element of ``vprop`` |
|---|
| 672 | should be inserted into the ungrouped list. |
|---|
| 673 | props : list of :class:`~graph_tool.PropertyMap` (optional, default: None) |
|---|
| 674 | If supplied, should contain a list of property maps to which ``vprop`` |
|---|
| 675 | should be ungroupped. |
|---|
| 676 | |
|---|
| 677 | Returns |
|---|
| 678 | ------- |
|---|
| 679 | props : list of :class:`~graph_tool.PropertyMap` |
|---|
| 680 | A list of property maps with the ungrouped values of ``vprop``. |
|---|
| 681 | |
|---|
| 682 | Examples |
|---|
| 683 | -------- |
|---|
| 684 | >>> from numpy.random import seed, randint |
|---|
| 685 | >>> from numpy import array |
|---|
| 686 | >>> seed(42) |
|---|
| 687 | >>> g = gt.random_graph(100, lambda: (3, 3)) |
|---|
| 688 | >>> prop = g.new_vertex_property("vector<int>") |
|---|
| 689 | >>> for v in g.vertices(): |
|---|
| 690 | ... prop[v] = randint(0, 100, 3) |
|---|
| 691 | >>> uprops = gt.ungroup_vector_property(prop, [0, 1, 2]) |
|---|
| 692 | >>> print prop[g.vertex(0)].a |
|---|
| 693 | [71 60 20] |
|---|
| 694 | >>> print array([p[g.vertex(0)] for p in uprops]) |
|---|
| 695 | [71 60 20] |
|---|
| 696 | """ |
|---|
| 697 | |
|---|
| 698 | g = vprop.get_graph() |
|---|
| 699 | _check_prop_vector(vprop, name="vprop", scalar=False) |
|---|
| 700 | k = vprop.key_type() |
|---|
| 701 | value_type = vprop.value_type().split("<")[1].split(">")[0] |
|---|
| 702 | if props == None: |
|---|
| 703 | if k == 'v': |
|---|
| 704 | props = [g.new_vertex_property(value_type) for i in pos] |
|---|
| 705 | elif k == 'e': |
|---|
| 706 | props = [g.new_edge_property(value_type) for i in pos] |
|---|
| 707 | else: |
|---|
| 708 | props = [g.new_graph_property(value_type) for i in pos] |
|---|
| 709 | |
|---|
| 710 | for i, p in enumerate(pos): |
|---|
| 711 | if props[i].key_type() != k: |
|---|
| 712 | raise ValueError("'props' must be of the same key type as 'vprop'.") |
|---|
| 713 | |
|---|
| 714 | if k != 'g': |
|---|
| 715 | g.stash_filter(directed=True, reversed=True) |
|---|
| 716 | g.set_directed(True) |
|---|
| 717 | g.set_reversed(False) |
|---|
| 718 | libcore.ungroup_vector_property(g._Graph__graph, |
|---|
| 719 | _prop(k, g, vprop), |
|---|
| 720 | _prop(k, g, props[i]), |
|---|
| 721 | p, k == 'e') |
|---|
| 722 | g.pop_filter(directed=True, reversed=True) |
|---|
| 723 | else: |
|---|
| 724 | if len(vprop[g]) <= pos[i]: |
|---|
| 725 | vprop[g].resize(pos[i] + 1) |
|---|
| 726 | props[i][g] = vprop[g][pos[i]] |
|---|
| 727 | return props |
|---|
| 728 | |
|---|
| 729 | |
|---|
| 730 | class PropertyDict(dict): |
|---|
| 731 | """Wrapper for the dict of vertex, graph or edge properties, which sets the |
|---|
| 732 | value on the property map when changed in the dict.""" |
|---|
| 733 | def __init__(self, g, old, get_func, set_func, del_func): |
|---|
| 734 | dict.__init__(self) |
|---|
| 735 | dict.update(self, old) |
|---|
| 736 | self.g = g |
|---|
| 737 | self.get_func = get_func |
|---|
| 738 | self.set_func = set_func |
|---|
| 739 | self.del_func = del_func |
|---|
| 740 | |
|---|
| 741 | def __setitem__(self, key, val): |
|---|
| 742 | if self.set_func != None: |
|---|
| 743 | self.set_func(self.g, key, val) |
|---|
| 744 | else: |
|---|
| 745 | raise KeyError("Property dict cannot be set") |
|---|
| 746 | dict.__setitem__(self, key, val) |
|---|
| 747 | |
|---|
| 748 | def __delitem__(self, key): |
|---|
| 749 | self.del_func(self.g, key) |
|---|
| 750 | dict.__delitem__(self, key) |
|---|
| 751 | |
|---|
| 752 | ################################################################################ |
|---|
| 753 | # Graph class |
|---|
| 754 | # The main graph interface |
|---|
| 755 | ################################################################################ |
|---|
| 756 | |
|---|
| 757 | from libgraph_tool_core import Vertex, Edge, Vector_bool, Vector_int32_t, \ |
|---|
| 758 | Vector_int64_t, Vector_double, Vector_long_double, Vector_string, \ |
|---|
| 759 | new_vertex_property, new_edge_property, new_graph_property |
|---|
| 760 | |
|---|
| 761 | |
|---|
| 762 | class Graph(object): |
|---|
| 763 | """Generic multigraph class. |
|---|
| 764 | |
|---|
| 765 | This class encapsulates either a directed multigraph (default or if |
|---|
| 766 | ``directed=True``) or an undirected multigraph (if ``directed=False``), |
|---|
| 767 | with optional internal edge, vertex or graph properties. |
|---|
| 768 | |
|---|
| 769 | If ``g`` is specified, the graph (and its internal properties) will be |
|---|
| 770 | copied. |
|---|
| 771 | |
|---|
| 772 | If ``prune`` is set to True, and ``g`` is specified, only the filtered graph |
|---|
| 773 | will be copied, and the new graph object will not be filtered. Optionally, a |
|---|
| 774 | tuple of three booleans can be passed as value to ``prune``, to specify a |
|---|
| 775 | different behavior to vertex, edge, and reversal filters, respectively. |
|---|
| 776 | |
|---|
| 777 | The graph is implemented as an `adjacency list`_, where both vertex and edge |
|---|
| 778 | lists are C++ STL vectors. |
|---|
| 779 | |
|---|
| 780 | .. _adjacency list: http://en.wikipedia.org/wiki/Adjacency_list |
|---|
| 781 | |
|---|
| 782 | """ |
|---|
| 783 | |
|---|
| 784 | def __init__(self, g=None, directed=True, prune=False): |
|---|
| 785 | self.__properties = {} |
|---|
| 786 | self.__known_properties = [] |
|---|
| 787 | self.__filter_state = {"reversed": False, |
|---|
| 788 | "edge_filter": (None, False), |
|---|
| 789 | "vertex_filter": (None, False), |
|---|
| 790 | "directed": True} |
|---|
| 791 | self.__stashed_filter_state = [] |
|---|
| 792 | |
|---|
| 793 | if g is None: |
|---|
| 794 | self.__graph = libcore.GraphInterface() |
|---|
| 795 | self.set_directed(directed) |
|---|
| 796 | else: |
|---|
| 797 | if isinstance(prune, bool): |
|---|
| 798 | vprune = eprune = rprune = prune |
|---|
| 799 | else: |
|---|
| 800 | vprune, eprune, rprune = prune |
|---|
| 801 | if not (vprune or eprune or rprune): |
|---|
| 802 | g.stash_filter(vertex=vprune, edge=vprune, reversed=rprune) |
|---|
| 803 | self.__graph = libcore.GraphInterface(g.__graph, False) |
|---|
| 804 | if not (vprune or eprune or rprune): |
|---|
| 805 | g.pop_filter(vertex=vprune, edge=vprune, reversed=rprune) |
|---|
| 806 | |
|---|
| 807 | for k, v in g.__properties.iteritems(): |
|---|
| 808 | new_p = self.new_property(v.key_type(), v.value_type()) |
|---|
| 809 | self.copy_property(v, new_p, g=g) |
|---|
| 810 | self.properties[k] = new_p |
|---|
| 811 | |
|---|
| 812 | self.__stashed_filter_state = [self.get_filter_state()] |
|---|
| 813 | |
|---|
| 814 | if not vprune: |
|---|
| 815 | v_filt, v_rev = g.__filter_state["vertex_filter"] |
|---|
| 816 | if v_filt != None: |
|---|
| 817 | if v_filt not in g.vertex_properties.values(): |
|---|
| 818 | new_filt = self.new_vertex_property("bool") |
|---|
| 819 | self.copy_property(v_filt, new_filt) |
|---|
| 820 | |
|---|
| 821 | else: |
|---|
| 822 | for k, v in g.vertex_properties.iteritems(): |
|---|
| 823 | if v == v_filt: |
|---|
| 824 | new_filt = self.vertex_properties[k] |
|---|
| 825 | self.__stashed_filter_state[0]["vertex_filter"] = (new_filt, |
|---|
| 826 | v_rev) |
|---|
| 827 | if not eprune: |
|---|
| 828 | e_filt, e_rev = g.__filter_state["edge_filter"] |
|---|
| 829 | if e_filt != None: |
|---|
| 830 | if e_filt not in g.edge_properties.values(): |
|---|
| 831 | new_filt = self.new_edge_property("bool") |
|---|
| 832 | self.copy_property(e_filt, new_filt) |
|---|
| 833 | |
|---|
| 834 | else: |
|---|
| 835 | for k, v in g.edge_properties.iteritems(): |
|---|
| 836 | if v == e_filt: |
|---|
| 837 | new_filt = self.edge_properties[k] |
|---|
| 838 | self.__stashed_filter_state[0]["edge_filter"] = (new_filt, |
|---|
| 839 | e_rev) |
|---|
| 840 | if not rprune: |
|---|
| 841 | self.__stashed_filter_state[0]["reversed"] = g.is_reversed() |
|---|
| 842 | |
|---|
| 843 | # directedness is always a filter |
|---|
| 844 | self.__stashed_filter_state[0]["directed"] = g.is_directed() |
|---|
| 845 | |
|---|
| 846 | self.pop_filter() |
|---|
| 847 | |
|---|
| 848 | if vprune or eprune: |
|---|
| 849 | self.reindex_edges() |
|---|
| 850 | |
|---|
| 851 | # internal index maps |
|---|
| 852 | self.__vertex_index = \ |
|---|
| 853 | PropertyMap(libcore.get_vertex_index(self.__graph), self, "v") |
|---|
| 854 | self.__edge_index = \ |
|---|
| 855 | PropertyMap(libcore.get_edge_index(self.__graph), self, "e") |
|---|
| 856 | |
|---|
| 857 | # modification permissions |
|---|
| 858 | self.__perms = {"add_edge": True, "del_edge": True, |
|---|
| 859 | "add_vertex": True, "del_vertex": True} |
|---|
| 860 | |
|---|
| 861 | def copy(self): |
|---|
| 862 | """Return a deep copy of self. All :ref:`internal property maps <sec_internal_props>` |
|---|
| 863 | are also copied.""" |
|---|
| 864 | return Graph(self) |
|---|
| 865 | |
|---|
| 866 | def __repr__(self): |
|---|
| 867 | # provide more useful information |
|---|
| 868 | d = "directed" if self.is_directed() else "undirected" |
|---|
| 869 | fr = ", reversed" if self.is_reversed() and self.is_directed() else "" |
|---|
| 870 | f = "" |
|---|
| 871 | if self.get_edge_filter()[0] is not None: |
|---|
| 872 | f += ", edges filtered by %s" % (str(self.get_edge_filter())) |
|---|
| 873 | if self.get_vertex_filter()[0] is not None: |
|---|
| 874 | f += ", vertices filtered by %s" % (str(self.get_vertex_filter())) |
|---|
| 875 | n = self.num_vertices() |
|---|
| 876 | e = self.num_edges() |
|---|
| 877 | return "<%s object, %s%s, with %d %s and %d edge%s%s at 0x%x>"\ |
|---|
| 878 | % (type(self).__name__, d, fr, n, |
|---|
| 879 | "vertex" if n == 1 else "vertices", e, "" if e == 1 else "s", |
|---|
| 880 | f, id(self)) |
|---|
| 881 | |
|---|
| 882 | # Graph access |
|---|
| 883 | # ============ |
|---|
| 884 | |
|---|
| 885 | def __check_perms(self, ptype): |
|---|
| 886 | if not self.__perms[ptype]: |
|---|
| 887 | raise RuntimeError("the graph cannot be modified at this point!") |
|---|
| 888 | |
|---|
| 889 | def vertices(self): |
|---|
| 890 | """Return an :meth:`iterator <iterator.__iter__>` over the vertices. |
|---|
| 891 | |
|---|
| 892 | .. note:: |
|---|
| 893 | |
|---|
| 894 | The order of the vertices traversed by the iterator **always** |
|---|
| 895 | corresponds to the vertex index ordering, as given by the |
|---|
| 896 | :attr:`~graph_tool.Graph.vertex_index` property map. |
|---|
| 897 | |
|---|
| 898 | Examples |
|---|
| 899 | -------- |
|---|
| 900 | >>> g = gt.Graph() |
|---|
| 901 | >>> vlist = g.add_vertex(5) |
|---|
| 902 | >>> vlist2 = [] |
|---|
| 903 | >>> for v in g.vertices(): |
|---|
| 904 | ... vlist2.append(v) |
|---|
| 905 | ... |
|---|
| 906 | >>> assert(vlist == vlist2) |
|---|
| 907 | |
|---|
| 908 | """ |
|---|
| 909 | return libcore.get_vertices(weakref.ref(self.__graph)) |
|---|
| 910 | |
|---|
| 911 | def vertex(self, i, use_index=True): |
|---|
| 912 | """Return the vertex with index ``i``. If ``use_index=False``, the |
|---|
| 913 | ``i``-th vertex is returned (which can differ from the vertex with index |
|---|
| 914 | ``i`` in case of filtered graphs). """ |
|---|
| 915 | if use_index: |
|---|
| 916 | self.stash_filter(vertex=True) |
|---|
| 917 | try: |
|---|
| 918 | v = libcore.get_vertex(weakref.ref(self.__graph), int(i)) |
|---|
| 919 | finally: |
|---|
| 920 | if use_index: |
|---|
| 921 | self.pop_filter(vertex=True) |
|---|
| 922 | return v |
|---|
| 923 | |
|---|
| 924 | def edge(self, s, t, all_edges=False): |
|---|
| 925 | """Return the edge from vertex ``s`` to ``t``, if it exists. If |
|---|
| 926 | ``all_edges=True`` then a list is returned with all the parallel edges |
|---|
| 927 | from ``s`` to ``t``, otherwise only one edge is returned. |
|---|
| 928 | |
|---|
| 929 | This operation will take :math:`O(k(s))` time, where :math:`k(s)` is the |
|---|
| 930 | out-degree of vertex :math:`s`. |
|---|
| 931 | """ |
|---|
| 932 | s = self.vertex(int(s)) |
|---|
| 933 | t = self.vertex(int(t)) |
|---|
| 934 | edges = [] |
|---|
| 935 | for e in s.out_edges(): |
|---|
| 936 | if e.target() == t: |
|---|
| 937 | if not all_edges: |
|---|
| 938 | return e |
|---|
| 939 | edges.append(e) |
|---|
| 940 | if all_edges: |
|---|
| 941 | return edges |
|---|
| 942 | return None |
|---|
| 943 | |
|---|
| 944 | def edges(self): |
|---|
| 945 | """Return an :meth:`iterator <iterator.__iter__>` over the edges. |
|---|
| 946 | |
|---|
| 947 | .. note:: |
|---|
| 948 | |
|---|
| 949 | The order of the edges traversed by the iterator **does not** |
|---|
| 950 | necessesarly correspond to the edge index ordering, as given by the |
|---|
| 951 | :attr:`~graph_tool.Graph.edge_index` property map. This will only |
|---|
| 952 | happen after :meth:`~graph_tool.Graph.reindex_edges` is called, or in |
|---|
| 953 | certain situations such as just after a graph is loaded from a |
|---|
| 954 | file. However, further manipulation of the graph may destroy the |
|---|
| 955 | ordering. |
|---|
| 956 | |
|---|
| 957 | """ |
|---|
| 958 | return libcore.get_edges(weakref.ref(self.__graph)) |
|---|
| 959 | |
|---|
| 960 | def add_vertex(self, n=1): |
|---|
| 961 | """Add a vertex to the graph, and return it. If ``n > 1``, ``n`` |
|---|
| 962 | vertices are inserted and a list is returned.""" |
|---|
| 963 | self.__check_perms("add_vertex") |
|---|
| 964 | vlist = [libcore.add_vertex(weakref.ref(self.__graph)) \ |
|---|
| 965 | for i in xrange(0, n)] |
|---|
| 966 | if n == 1: |
|---|
| 967 | return vlist[0] |
|---|
| 968 | return vlist |
|---|
| 969 | |
|---|
| 970 | def remove_vertex(self, vertex): |
|---|
| 971 | """Remove a vertex from the graph.""" |
|---|
| 972 | self.__check_perms("del_vertex") |
|---|
| 973 | index = self.vertex_index[vertex] |
|---|
| 974 | for pmap in self.__known_properties: |
|---|
| 975 | if pmap[0] == "v" and pmap[1]() != None and pmap[1]().is_writable(): |
|---|
| 976 | self.__graph.ShiftVertexProperty(pmap[1]().get_map(), index) |
|---|
| 977 | self.clear_vertex(vertex) |
|---|
| 978 | libcore.remove_vertex(self.__graph, vertex) |
|---|
| 979 | |
|---|
| 980 | def remove_vertex_if(self, predicate): |
|---|
| 981 | """Remove all the vertices from the graph for which ``predicate(v)`` |
|---|
| 982 | evaluates to ``True``. """ |
|---|
| 983 | N = self.num_vertices() |
|---|
| 984 | for i in xrange(0, N): |
|---|
| 985 | v = self.vertex(N - i - 1) |
|---|
| 986 | if predicate(v): |
|---|
| 987 | self.remove_vertex(v) |
|---|
| 988 | |
|---|
| 989 | def clear_vertex(self, vertex): |
|---|
| 990 | """Remove all in and out-edges from the given vertex.""" |
|---|
| 991 | del_es = set() |
|---|
| 992 | for e in vertex.all_edges(): |
|---|
| 993 | del_es.add(e) |
|---|
| 994 | for e in del_es: |
|---|
| 995 | self.remove_edge(e) |
|---|
| 996 | |
|---|
| 997 | def add_edge(self, source, target): |
|---|
| 998 | """Add a new edge from ``source`` to ``target`` to the graph, and return |
|---|
| 999 | it.""" |
|---|
| 1000 | self.__check_perms("add_edge") |
|---|
| 1001 | return libcore.add_edge(weakref.ref(self.__graph), source, target) |
|---|
| 1002 | |
|---|
| 1003 | def remove_edge(self, edge): |
|---|
| 1004 | """Remove an edge from the graph.""" |
|---|
| 1005 | self.__check_perms("del_edge") |
|---|
| 1006 | return libcore.remove_edge(self.__graph, edge) |
|---|
| 1007 | |
|---|
| 1008 | def remove_edge_if(self, predicate): |
|---|
| 1009 | """Remove all edges from the graph, for which ``predicate(e)`` evaluates |
|---|
| 1010 | to ``True``.""" |
|---|
| 1011 | for v in self.vertices(): |
|---|
| 1012 | del_es = [] |
|---|
| 1013 | for e in v.out_edges(): |
|---|
| 1014 | if predicate(e): |
|---|
| 1015 | del_es.append(e) |
|---|
| 1016 | for e in del_es: |
|---|
| 1017 | self.remove_edge(e) |
|---|
| 1018 | |
|---|
| 1019 | def clear(self): |
|---|
| 1020 | """Remove all vertices and edges from the graph.""" |
|---|
| 1021 | self.__check_perms("del_vertex") |
|---|
| 1022 | self.__check_perms("del_edge") |
|---|
| 1023 | self.__graph.Clear() |
|---|
| 1024 | |
|---|
| 1025 | def clear_edges(self): |
|---|
| 1026 | """Remove all edges from the graph.""" |
|---|
| 1027 | self.__check_perms("del_edge") |
|---|
| 1028 | self.__graph.ClearEdges() |
|---|
| 1029 | |
|---|
| 1030 | # Internal property maps |
|---|
| 1031 | # ====================== |
|---|
| 1032 | |
|---|
| 1033 | # all properties |
|---|
| 1034 | def __get_properties(self): |
|---|
| 1035 | return PropertyDict(self, self.__properties, |
|---|
| 1036 | lambda g, k: g.__properties[k], |
|---|
| 1037 | lambda g, k, v: g.__set_property(k[0], k[1], v), |
|---|
| 1038 | lambda g, k: g.__del_property(k[0], k[1])) |
|---|
| 1039 | |
|---|
| 1040 | @_limit_args({"t": ["v", "e", "g"]}) |
|---|
| 1041 | @_require("k", str) |
|---|
| 1042 | @_require("v", PropertyMap) |
|---|
| 1043 | def __set_property(self, t, k, v): |
|---|
| 1044 | if t != v.key_type(): |
|---|
| 1045 | raise ValueError("wrong key type for property map") |
|---|
| 1046 | self.__properties[(t, k)] = v |
|---|
| 1047 | |
|---|
| 1048 | @_limit_args({"t": ["v", "e", "g"]}) |
|---|
| 1049 | @_require("k", str) |
|---|
| 1050 | def __del_property(self, t, k): |
|---|
| 1051 | del self.__properties[(t, k)] |
|---|
| 1052 | |
|---|
| 1053 | properties = property(__get_properties, |
|---|
| 1054 | doc= |
|---|
| 1055 | """Dictionary of internal properties. Keys must always be a tuple, where the |
|---|
| 1056 | first element if a string from the set {'v', 'e', 'g'}, representing a |
|---|
| 1057 | vertex, edge or graph property, respectively, and the second element is the |
|---|
| 1058 | name of the property map. |
|---|
| 1059 | |
|---|
| 1060 | Examples |
|---|
| 1061 | -------- |
|---|
| 1062 | >>> g = gt.Graph() |
|---|
| 1063 | >>> g.properties[("e", "foo")] = g.new_edge_property("vector<double>") |
|---|
| 1064 | >>> del g.properties[("e", "foo")] |
|---|
| 1065 | """) |
|---|
| 1066 | |
|---|
| 1067 | def __get_specific_properties(self, t): |
|---|
| 1068 | props = dict([(k[1], v) for k, v in self.__properties.iteritems() \ |
|---|
| 1069 | if k[0] == t]) |
|---|
| 1070 | return props |
|---|
| 1071 | |
|---|
| 1072 | # vertex properties |
|---|
| 1073 | def __get_vertex_properties(self): |
|---|
| 1074 | return PropertyDict(self, self.__get_specific_properties("v"), |
|---|
| 1075 | lambda g, k: g.__properties[("v", k)], |
|---|
| 1076 | lambda g, k, v: g.__set_property("v", k, v), |
|---|
| 1077 | lambda g, k: g.__del_property("v", k)) |
|---|
| 1078 | vertex_properties = property(__get_vertex_properties, |
|---|
| 1079 | doc="Dictionary of internal vertex properties. The keys are the property names.") |
|---|
| 1080 | |
|---|
| 1081 | # edge properties |
|---|
| 1082 | def __get_edge_properties(self): |
|---|
| 1083 | return PropertyDict(self, self.__get_specific_properties("e"), |
|---|
| 1084 | lambda g, k: g.__properties[("e", k)], |
|---|
| 1085 | lambda g, k, v: g.__set_property("e", k, v), |
|---|
| 1086 | lambda g, k: g.__del_property("e", k)) |
|---|
| 1087 | edge_properties = property(__get_edge_properties, |
|---|
| 1088 | doc="Dictionary of internal edge properties. The keys are the property names.") |
|---|
| 1089 | |
|---|
| 1090 | # graph properties |
|---|
| 1091 | def __get_graph_properties(self): |
|---|
| 1092 | return PropertyDict(self, self.__get_specific_properties("g"), |
|---|
| 1093 | lambda g, k: g.__properties[("g", k)], |
|---|
| 1094 | lambda g, k, v: g.__set_property("g", k, v), |
|---|
| 1095 | lambda g, k: g.__del_property("g", k)) |
|---|
| 1096 | graph_properties = property(__get_graph_properties, |
|---|
| 1097 | doc="Dictionary of internal graph properties. The keys are the property names.") |
|---|
| 1098 | |
|---|
| 1099 | def list_properties(self): |
|---|
| 1100 | """Print a list of all internal properties. |
|---|
| 1101 | |
|---|
| 1102 | Examples |
|---|
| 1103 | -------- |
|---|
| 1104 | >>> g = gt.Graph() |
|---|
| 1105 | >>> g.properties[("e", "foo")] = g.new_edge_property("vector<double>") |
|---|
| 1106 | >>> g.vertex_properties["foo"] = g.new_vertex_property("double") |
|---|
| 1107 | >>> g.vertex_properties["bar"] = g.new_vertex_property("python::object") |
|---|
| 1108 | >>> g.graph_properties["gnat"] = g.new_graph_property("string", "hi there!") |
|---|
| 1109 | >>> g.list_properties() |
|---|
| 1110 | gnat (graph) (type: string, val: hi there!) |
|---|
| 1111 | bar (vertex) (type: python::object) |
|---|
| 1112 | foo (vertex) (type: double) |
|---|
| 1113 | foo (edge) (type: vector<double>) |
|---|
| 1114 | """ |
|---|
| 1115 | |
|---|
| 1116 | if len(self.__properties) == 0: |
|---|
| 1117 | return |
|---|
| 1118 | w = max([len(x[0]) for x in self.__properties.keys()]) + 4 |
|---|
| 1119 | w = w if w > 14 else 14 |
|---|
| 1120 | |
|---|
| 1121 | for k, v in self.__properties.iteritems(): |
|---|
| 1122 | if k[0] == "g": |
|---|
| 1123 | print "%%-%ds (graph) (type: %%s, val: %%s)" % w % \ |
|---|
| 1124 | (k[1], v.value_type(), str(v[self])) |
|---|
| 1125 | for k, v in self.__properties.iteritems(): |
|---|
| 1126 | if k[0] == "v": |
|---|
| 1127 | print "%%-%ds (vertex) (type: %%s)" % w % (k[1], |
|---|
| 1128 | v.value_type()) |
|---|
| 1129 | for k, v in self.__properties.iteritems(): |
|---|
| 1130 | if k[0] == "e": |
|---|
| 1131 | print "%%-%ds (edge) (type: %%s)" % w % (k[1], |
|---|
| 1132 | v.value_type()) |
|---|
| 1133 | |
|---|
| 1134 | # index properties |
|---|
| 1135 | |
|---|
| 1136 | def _get_vertex_index(self): |
|---|
| 1137 | return self.__vertex_index |
|---|
| 1138 | vertex_index = property(_get_vertex_index, |
|---|
| 1139 | doc="""Vertex index map. |
|---|
| 1140 | |
|---|
| 1141 | It maps for each vertex in the graph an unique |
|---|
| 1142 | integer in the range [0, :meth:`~graph_tool.Graph.num_vertices` - 1]. |
|---|
| 1143 | |
|---|
| 1144 | .. note:: |
|---|
| 1145 | |
|---|
| 1146 | This is a special instance of a :class:`~graph_tool.PropertyMap` |
|---|
| 1147 | class, which is **immutable**, and cannot be |
|---|
| 1148 | accessed as an array.""") |
|---|
| 1149 | |
|---|
| 1150 | def _get_edge_index(self): |
|---|
| 1151 | return self.__edge_index |
|---|
| 1152 | edge_index = property(_get_edge_index, doc="""Edge index map. |
|---|
| 1153 | |
|---|
| 1154 | It maps for each edge in the graph an unique |
|---|
| 1155 | integer. |
|---|
| 1156 | |
|---|
| 1157 | .. warning:: |
|---|
| 1158 | |
|---|
| 1159 | Differently from :attr:`~graph_tool.Graph.vertex_index`, |
|---|
| 1160 | this is a **regular** instance of a :class:`~graph_tool.PropertyMap` |
|---|
| 1161 | class, and is therefore **mutable**! |
|---|
| 1162 | |
|---|
| 1163 | Additionally, the indexes may not necessarily |
|---|
| 1164 | lie in the range [0, :meth:`~graph_tool.Graph.num_edges` - 1]. |
|---|
| 1165 | However this will always happen whenever no |
|---|
| 1166 | edges are deleted from the graph. |
|---|
| 1167 | |
|---|
| 1168 | The internal consistency expected by most |
|---|
| 1169 | algorithms and the proper functioning of |
|---|
| 1170 | property maps assume that the indexes are unique |
|---|
| 1171 | and constant, which is guaranteed by the |
|---|
| 1172 | library. Therefore it is recommended **never** |
|---|
| 1173 | to modify these values, unless you know what you |
|---|
| 1174 | are doing.""") |
|---|
| 1175 | |
|---|
| 1176 | def _get_max_edge_index(self): |
|---|
| 1177 | return self.__graph.GetMaxEdgeIndex() |
|---|
| 1178 | max_edge_index = property(_get_max_edge_index, |
|---|
| 1179 | doc="The maximum value of the edge index map.") |
|---|
| 1180 | |
|---|
| 1181 | def reindex_edges(self): |
|---|
| 1182 | """ |
|---|
| 1183 | Reset the edge indexes so that they lie in the [0, :meth:`~graph_tool.Graph.num_edges` - 1] |
|---|
| 1184 | range. The index ordering will be compatible with the sequence returned |
|---|
| 1185 | by the :meth:`~graph_tool.Graph.edges` function. |
|---|
| 1186 | |
|---|
| 1187 | .. WARNING:: |
|---|
| 1188 | |
|---|
| 1189 | Calling this function will invalidate all existing edge property |
|---|
| 1190 | maps, if the index ordering is modified! The property maps will still |
|---|
| 1191 | be usable, but their contents will still be tied to the old indexes, |
|---|
| 1192 | and thus may become scrambled. |
|---|
| 1193 | """ |
|---|
| 1194 | self.__graph.ReIndexEdges() |
|---|
| 1195 | |
|---|
| 1196 | # Property map creation |
|---|
| 1197 | |
|---|
| 1198 | def new_property(self, key_type, value_type): |
|---|
| 1199 | """Create a new (uninitialized) vertex property map of key type |
|---|
| 1200 | ``key_type`` (``v``, ``e`` or ``g``), value type ``value_type``, and |
|---|
| 1201 | return it. |
|---|
| 1202 | """ |
|---|
| 1203 | if key_type == "v" or key_type == "vertex": |
|---|
| 1204 | return self.new_vertex_property(value_type) |
|---|
| 1205 | if key_type == "e" or key_type == "edge": |
|---|
| 1206 | return self.new_edge_property(value_type) |
|---|
| 1207 | if key_type == "g" or key_type == "graph": |
|---|
| 1208 | return self.new_graph_property(value_type) |
|---|
| 1209 | raise ValueError("unknown key type: " + key_type) |
|---|
| 1210 | |
|---|
| 1211 | def new_vertex_property(self, value_type): |
|---|
| 1212 | """Create a new (uninitialized) vertex property map of type |
|---|
| 1213 | ``value_type``, and return it.""" |
|---|
| 1214 | return PropertyMap(new_vertex_property(_type_alias(value_type), |
|---|
| 1215 | self.__graph.GetVertexIndex()), |
|---|
| 1216 | self, "v") |
|---|
| 1217 | |
|---|
| 1218 | def new_edge_property(self, value_type): |
|---|
| 1219 | """Create a new (uninitialized) edge property map of type |
|---|
| 1220 | ``value_type``, and return it.""" |
|---|
| 1221 | return PropertyMap(new_edge_property(_type_alias(value_type), |
|---|
| 1222 | self.__graph.GetEdgeIndex()), |
|---|
| 1223 | self, "e") |
|---|
| 1224 | |
|---|
| 1225 | def new_graph_property(self, value_type, val=None): |
|---|
| 1226 | """Create a new graph property map of type ``value_type``, and return |
|---|
| 1227 | it. If ``val`` is not None, the property is initialized to its value.""" |
|---|
| 1228 | prop = PropertyMap(new_graph_property(_type_alias(value_type), |
|---|
| 1229 | self.__graph.GetGraphIndex()), |
|---|
| 1230 | self, "g", lambda k: k.__graph) |
|---|
| 1231 | if val != None: |
|---|
| 1232 | prop[self] = val |
|---|
| 1233 | return prop |
|---|
| 1234 | |
|---|
| 1235 | # property map copying |
|---|
| 1236 | @_require("src", PropertyMap) |
|---|
| 1237 | @_require("tgt", (PropertyMap, type(None))) |
|---|
| 1238 | def copy_property(self, src, tgt=None, value_type=None, g=None): |
|---|
| 1239 | """Copy contents of ``src`` property to ``tgt`` property. If ``tgt`` is |
|---|
| 1240 | None, then a new property map of the same type (or with the type given |
|---|
| 1241 | by the optional ``value_type`` parameter) is created, and returned. The |
|---|
| 1242 | optional parameter g specifies the (identical) source graph to copy |
|---|
| 1243 | properties from (defaults to self). |
|---|
| 1244 | """ |
|---|
| 1245 | if tgt is None: |
|---|
| 1246 | tgt = self.new_property(src.key_type(), |
|---|
| 1247 | (src.value_type() |
|---|
| 1248 | if value_type == None else value_type)) |
|---|
| 1249 | ret = tgt |
|---|
| 1250 | else: |
|---|
| 1251 | ret = None |
|---|
| 1252 | |
|---|
| 1253 | if src.key_type() != tgt.key_type(): |
|---|
| 1254 | raise ValueError("source and target properties must have the same" + |
|---|
| 1255 | " key type") |
|---|
| 1256 | if g is None: |
|---|
| 1257 | g = self |
|---|
| 1258 | if g is not self: |
|---|
| 1259 | g.stash_filter(directed=True, reversed=True) |
|---|
| 1260 | self.stash_filter(directed=True, reversed=True) |
|---|
| 1261 | if src.key_type() == "v": |
|---|
| 1262 | self.__graph.CopyVertexProperty(g.__graph, _prop("v", g, src), |
|---|
| 1263 | _prop("v", self, tgt)) |
|---|
| 1264 | elif src.key_type() == "e": |
|---|
| 1265 | self.__graph.CopyEdgeProperty(g.__graph, _prop("e", g, src), |
|---|
| 1266 | _prop("e", self, tgt)) |
|---|
| 1267 | else: |
|---|
| 1268 | tgt[self] = src[g] |
|---|
| 1269 | self.pop_filter(directed=True, reversed=True) |
|---|
| 1270 | if g is not self: |
|---|
| 1271 | g.pop_filter(directed=True, reversed=True) |
|---|
| 1272 | return ret |
|---|
| 1273 | |
|---|
| 1274 | # degree property map |
|---|
| 1275 | @_limit_args({"deg": ["in", "out", "total"]}) |
|---|
| 1276 | def degree_property_map(self, deg): |
|---|
| 1277 | """Create and return a vertex property map containing the degree type |
|---|
| 1278 | given by ``deg``.""" |
|---|
| 1279 | return PropertyMap(self.__graph.DegreeMap(deg), self, "v") |
|---|
| 1280 | |
|---|
| 1281 | # I/O operations |
|---|
| 1282 | # ============== |
|---|
| 1283 | def load(self, file_name, file_format="auto"): |
|---|
| 1284 | """Load graph from ``file_name`` (which can be either a string or a |
|---|
| 1285 | file-like object). The format is guessed from ``file_name``, or can be |
|---|
| 1286 | specified by ``file_format``, which can be either "xml" or "dot". """ |
|---|
| 1287 | |
|---|
| 1288 | if type(file_name) == str: |
|---|
| 1289 | file_name = os.path.expanduser(file_name) |
|---|
| 1290 | if file_format == 'auto' and isinstance(file_name, str): |
|---|
| 1291 | if file_name.endswith(".xml") or file_name.endswith(".xml.gz") or \ |
|---|
| 1292 | file_name.endswith(".xml.bz2"): |
|---|
| 1293 | file_format = "xml" |
|---|
| 1294 | elif file_name.endswith(".dot") or file_name.endswith(".dot.gz") or \ |
|---|
| 1295 | file_name.endswith(".dot.bz2"): |
|---|
| 1296 | file_format = "dot" |
|---|
| 1297 | else: |
|---|
| 1298 | raise ValueError("cannot determine file format of: " + file_name) |
|---|
| 1299 | elif file_format == "auto": |
|---|
| 1300 | file_format = "xml" |
|---|
| 1301 | if isinstance(file_name, str): |
|---|
| 1302 | props = self.__graph.ReadFromFile(file_name, None, file_format) |
|---|
| 1303 | else: |
|---|
| 1304 | props = self.__graph.ReadFromFile("", file_name, file_format) |
|---|
| 1305 | for name, prop in props[0].iteritems(): |
|---|
| 1306 | self.vertex_properties[name] = PropertyMap(prop, self, "v") |
|---|
| 1307 | for name, prop in props[1].iteritems(): |
|---|
| 1308 | self.edge_properties[name] = PropertyMap(prop, self, "e") |
|---|
| 1309 | for name, prop in props[2].iteritems(): |
|---|
| 1310 | self.graph_properties[name] = PropertyMap(prop, self, "g", |
|---|
| 1311 | lambda k: k.__graph) |
|---|
| 1312 | |
|---|
| 1313 | def save(self, file_name, file_format="auto"): |
|---|
| 1314 | """Save graph to ``file_name`` (which can be either a string or a |
|---|
| 1315 | file-like object). The format is guessed from the ``file_name``, or can |
|---|
| 1316 | be specified by ``file_format``, which can be either "xml" or "dot". """ |
|---|
| 1317 | |
|---|
| 1318 | if type(file_name) == str: |
|---|
| 1319 | file_name = os.path.expanduser(file_name) |
|---|
| 1320 | if file_format == 'auto' and isinstance(file_name, str): |
|---|
| 1321 | if file_name.endswith(".xml") or file_name.endswith(".xml.gz") or \ |
|---|
| 1322 | file_name.endswith(".xml.bz2"): |
|---|
| 1323 | file_format = "xml" |
|---|
| 1324 | elif file_name.endswith(".dot") or file_name.endswith(".dot.gz") or \ |
|---|
| 1325 | file_name.endswith(".dot.bz2"): |
|---|
| 1326 | file_format = "dot" |
|---|
| 1327 | else: |
|---|
| 1328 | raise ValueError("cannot determine file file_format of: " + file_name) |
|---|
| 1329 | elif file_format == "auto": |
|---|
| 1330 | file_format = "xml" |
|---|
| 1331 | props = [(name[1], prop._PropertyMap__map) for name, prop in \ |
|---|
| 1332 | self.__properties.iteritems()] |
|---|
| 1333 | if isinstance(file_name, str): |
|---|
| 1334 | self.__graph.WriteToFile(file_name, None, file_format, props) |
|---|
| 1335 | else: |
|---|
| 1336 | self.__graph.WriteToFile("", file_name, file_format, props) |
|---|
| 1337 | |
|---|
| 1338 | # Directedness |
|---|
| 1339 | # ============ |
|---|
| 1340 | |
|---|
| 1341 | def set_directed(self, is_directed): |
|---|
| 1342 | """Set the directedness of the graph.""" |
|---|
| 1343 | self.__graph.SetDirected(is_directed) |
|---|
| 1344 | |
|---|
| 1345 | def is_directed(self): |
|---|
| 1346 | """Get the directedness of the graph.""" |
|---|
| 1347 | return self.__graph.GetDirected() |
|---|
| 1348 | |
|---|
| 1349 | # Reversedness |
|---|
| 1350 | # ============ |
|---|
| 1351 | |
|---|
| 1352 | def set_reversed(self, is_reversed): |
|---|
| 1353 | """Reverse the direction of the edges, if ``is_reversed`` is ``True``, |
|---|
| 1354 | or maintain the original direction otherwise.""" |
|---|
| 1355 | self.__graph.SetReversed(is_reversed) |
|---|
| 1356 | |
|---|
| 1357 | def is_reversed(self): |
|---|
| 1358 | """Return ``True`` if the edges are reversed, and ``False`` otherwise. |
|---|
| 1359 | """ |
|---|
| 1360 | return self.__graph.GetReversed() |
|---|
| 1361 | |
|---|
| 1362 | # Filtering |
|---|
| 1363 | # ========= |
|---|
| 1364 | |
|---|
| 1365 | def set_vertex_filter(self, prop, inverted=False): |
|---|
| 1366 | """Choose vertex boolean filter property. Only the vertices with value |
|---|
| 1367 | different than zero are kept in the filtered graph. If the ``inverted`` |
|---|
| 1368 | option is supplied with value ``True``, only the vertices with value |
|---|
| 1369 | zero are kept. If the supplied property is ``None``, any previous |
|---|
| 1370 | filtering is removed.""" |
|---|
| 1371 | |
|---|
| 1372 | self.__graph.SetVertexFilterProperty(_prop("v", self, prop), |
|---|
| 1373 | inverted) |
|---|
| 1374 | self.__filter_state["vertex_filter"] = (prop, inverted) |
|---|
| 1375 | |
|---|
| 1376 | def get_vertex_filter(self): |
|---|
| 1377 | """Return a tuple with the vertex filter property and bool value |
|---|
| 1378 | indicating whether or not it is inverted.""" |
|---|
| 1379 | return self.__filter_state["vertex_filter"] |
|---|
| 1380 | |
|---|
| 1381 | def set_edge_filter(self, prop, inverted=False): |
|---|
| 1382 | """Choose edge boolean filter property. Only the edges with value |
|---|
| 1383 | different than zero are kept in the filtered graph. If the ``inverted`` |
|---|
| 1384 | option is supplied with value ``True``, only the edges with value zero |
|---|
| 1385 | are kept. If the supplied property is ``None``, any previous filtering |
|---|
| 1386 | is removed.""" |
|---|
| 1387 | self.__graph.SetEdgeFilterProperty(_prop("e", self, prop), inverted) |
|---|
| 1388 | self.__filter_state["edge_filter"] = (prop, inverted) |
|---|
| 1389 | |
|---|
| 1390 | def get_edge_filter(self): |
|---|
| 1391 | """Return a tuple with the edge filter property and bool value |
|---|
| 1392 | indicating whether or not it is inverted.""" |
|---|
| 1393 | return self.__filter_state["edge_filter"] |
|---|
| 1394 | |
|---|
| 1395 | def purge_vertices(self): |
|---|
| 1396 | """Remove all vertices of the graph which are currently being filtered |
|---|
| 1397 | out, and return it to the unfiltered state.""" |
|---|
| 1398 | self.__graph.PurgeVertices() |
|---|
| 1399 | self.set_vertex_filter(None) |
|---|
| 1400 | |
|---|
| 1401 | def purge_edges(self): |
|---|
| 1402 | """Remove all edges of the graph which are currently being filtered out, |
|---|
| 1403 | and return it to the unfiltered state.""" |
|---|
| 1404 | self.__graph.PurgeEdges() |
|---|
| 1405 | self.set_edge_filter(None) |
|---|
| 1406 | |
|---|
| 1407 | def stash_filter(self, edge=False, vertex=False, directed=False, |
|---|
| 1408 | reversed=False, all=True): |
|---|
| 1409 | """Stash current filter state and set the graph to its unfiltered |
|---|
| 1410 | state. The optional keyword arguments specify which type of filter |
|---|
| 1411 | should be stashed.""" |
|---|
| 1412 | if edge or vertex or directed or reversed: |
|---|
| 1413 | all = False |
|---|
| 1414 | self.__stashed_filter_state.append(self.get_filter_state()) |
|---|
| 1415 | if libcore.graph_filtering_enabled(): |
|---|
| 1416 | if vertex or all: |
|---|
| 1417 | self.set_vertex_filter(None) |
|---|
| 1418 | if edge or all: |
|---|
| 1419 | self.set_edge_filter(None) |
|---|
| 1420 | if directed or all: |
|---|
| 1421 | self.set_directed(True) |
|---|
| 1422 | if reversed or all: |
|---|
| 1423 | self.set_reversed(False) |
|---|
| 1424 | |
|---|
| 1425 | def pop_filter(self, edge=False, vertex=False, directed=False, |
|---|
| 1426 | reversed=False, all=True): |
|---|
| 1427 | """Pop last stashed filter state. The optional keyword arguments specify |
|---|
| 1428 | which type of filter should be recovered.""" |
|---|
| 1429 | if edge or vertex or directed or reversed: |
|---|
| 1430 | all = False |
|---|
| 1431 | state = self.__stashed_filter_state.pop() |
|---|
| 1432 | if libcore.graph_filtering_enabled(): |
|---|
| 1433 | if vertex or all: |
|---|
| 1434 | self.set_vertex_filter(state["vertex_filter"][0], |
|---|
| 1435 | state["vertex_filter"][1]) |
|---|
| 1436 | if edge or all: |
|---|
| 1437 | self.set_edge_filter(state["edge_filter"][0], |
|---|
| 1438 | state["edge_filter"][1]) |
|---|
| 1439 | if directed or all: |
|---|
| 1440 | self.set_directed(state["directed"]) |
|---|
| 1441 | if reversed or all: |
|---|
| 1442 | self.set_reversed(state["reversed"]) |
|---|
| 1443 | |
|---|
| 1444 | def get_filter_state(self): |
|---|
| 1445 | """Return a copy of the filter state of the graph.""" |
|---|
| 1446 | self.__filter_state["directed"] = self.is_directed() |
|---|
| 1447 | self.__filter_state["reversed"] = self.is_reversed() |
|---|
| 1448 | return copy.copy(self.__filter_state) |
|---|
| 1449 | |
|---|
| 1450 | def set_filter_state(self, state): |
|---|
| 1451 | """Set the filter state of the graph.""" |
|---|
| 1452 | if libcore.graph_filtering_enabled(): |
|---|
| 1453 | self.set_vertex_filter(state["vertex_filter"][0], |
|---|
| 1454 | state["vertex_filter"][1]) |
|---|
| 1455 | self.set_edge_filter(state["edge_filter"][0], |
|---|
| 1456 | state["edge_filter"][1]) |
|---|
| 1457 | self.set_directed(state["directed"]) |
|---|
| 1458 | self.set_reversed(state["reversed"]) |
|---|
| 1459 | |
|---|
| 1460 | # Basic graph statistics |
|---|
| 1461 | # ====================== |
|---|
| 1462 | |
|---|
| 1463 | def num_vertices(self): |
|---|
| 1464 | """Get the number of vertices. |
|---|
| 1465 | |
|---|
| 1466 | .. note:: |
|---|
| 1467 | |
|---|
| 1468 | If the vertices are being filtered, this operation is |
|---|
| 1469 | :math:`O(N)`. Otherwise it is :math:`O(1)`. |
|---|
| 1470 | """ |
|---|
| 1471 | return self.__graph.GetNumberOfVertices() |
|---|
| 1472 | |
|---|
| 1473 | def num_edges(self): |
|---|
| 1474 | """Get the number of edges. |
|---|
| 1475 | |
|---|
| 1476 | .. note:: |
|---|
| 1477 | |
|---|
| 1478 | If the edges are being filtered, this operation is |
|---|
| 1479 | :math:`O(E)`. Otherwise it is :math:`O(1)`. |
|---|
| 1480 | """ |
|---|
| 1481 | return self.__graph.GetNumberOfEdges() |
|---|
| 1482 | |
|---|
| 1483 | # Pickling support |
|---|
| 1484 | # ================ |
|---|
| 1485 | |
|---|
| 1486 | def __getstate__(self): |
|---|
| 1487 | state = dict() |
|---|
| 1488 | sio = StringIO() |
|---|
| 1489 | if libcore.graph_filtering_enabled(): |
|---|
| 1490 | if self.get_vertex_filter()[0] != None: |
|---|
| 1491 | self.vertex_properties["_Graph__pickle__vfilter"] = \ |
|---|
| 1492 | self.get_vertex_filter()[0] |
|---|
| 1493 | state["vfilter"] = self.get_vertex_filter()[1] |
|---|
| 1494 | if self.get_edge_filter()[0] != None: |
|---|
| 1495 | self.edge_properties["_Graph__pickle__efilter"] = \ |
|---|
| 1496 | self.get_edge_filter()[0] |
|---|
| 1497 | state["efilter"] = self.get_edge_filter()[1] |
|---|
| 1498 | self.save(sio, "xml") |
|---|
| 1499 | state["blob"] = sio.getvalue() |
|---|
| 1500 | return state |
|---|
| 1501 | |
|---|
| 1502 | def __setstate__(self, state): |
|---|
| 1503 | self.__init__() |
|---|
| 1504 | blob = state["blob"] |
|---|
| 1505 | if blob != "": |
|---|
| 1506 | sio = StringIO(blob) |
|---|
| 1507 | self.load(sio, "xml") |
|---|
| 1508 | if "vfilt" in state: |
|---|
| 1509 | vprop = self.vertex_properties["_Graph__pickle__vfilter"] |
|---|
| 1510 | self.set_vertex_filter(vprop, state["vfilt"]) |
|---|
| 1511 | if "efilt" in state: |
|---|
| 1512 | eprop = self.edge_properties["_Graph__pickle__efilter"] |
|---|
| 1513 | self.set_edge_filter(eprop, state["efilt"]) |
|---|
| 1514 | |
|---|
| 1515 | |
|---|
| 1516 | def load_graph(file_name, file_format="auto"): |
|---|
| 1517 | """ |
|---|
| 1518 | Load a graph from ``file_name`` (which can be either a string or a file-like object). |
|---|
| 1519 | |
|---|
| 1520 | The format is guessed from ``file_name``, or can be specified by |
|---|
| 1521 | ``file_format``, which can be either "xml" or "dot". |
|---|
| 1522 | """ |
|---|
| 1523 | g = Graph() |
|---|
| 1524 | g.load(file_name, file_format=file_format) |
|---|
| 1525 | return g |
|---|
| 1526 | |
|---|
| 1527 | |
|---|
| 1528 | def value_types(): |
|---|
| 1529 | """Return a list of possible properties value types.""" |
|---|
| 1530 | return libcore.get_property_types() |
|---|
| 1531 | |
|---|
| 1532 | # Vertex and Edge Types |
|---|
| 1533 | # ===================== |
|---|
| 1534 | from libgraph_tool_core import Vertex, Edge |
|---|
| 1535 | |
|---|
| 1536 | Vertex.__doc__ = """Vertex descriptor. |
|---|
| 1537 | |
|---|
| 1538 | This class represents a vertex in a :class:`~graph_tool.Graph` instance. |
|---|
| 1539 | |
|---|
| 1540 | :class:`~graph_tool.Vertex` instances are hashable, and are convertible to |
|---|
| 1541 | integers, corresponding to its index (see :attr:`~graph_tool.Graph.vertex_index`). |
|---|
| 1542 | """ |
|---|
| 1543 | |
|---|
| 1544 | |
|---|
| 1545 | def _out_neighbours(self): |
|---|
| 1546 | """Return an iterator over the out-neighbours.""" |
|---|
| 1547 | for e in self.out_edges(): |
|---|
| 1548 | yield e.target() |
|---|
| 1549 | Vertex.out_neighbours = _out_neighbours |
|---|
| 1550 | |
|---|
| 1551 | |
|---|
| 1552 | def _in_neighbours(self): |
|---|
| 1553 | """Return an iterator over the in-neighbours.""" |
|---|
| 1554 | for e in self.in_edges(): |
|---|
| 1555 | yield e.source() |
|---|
| 1556 | Vertex.in_neighbours = _in_neighbours |
|---|
| 1557 | |
|---|
| 1558 | |
|---|
| 1559 | def _all_edges(self): |
|---|
| 1560 | """Return an iterator over all edges (both in or out).""" |
|---|
| 1561 | for e in self.out_edges(): |
|---|
| 1562 | yield e |
|---|
| 1563 | for e in self.in_edges(): |
|---|
| 1564 | yield e |
|---|
| 1565 | Vertex.all_edges = _all_edges |
|---|
| 1566 | |
|---|
| 1567 | |
|---|
| 1568 | def _all_neighbours(self): |
|---|
| 1569 | """Return an iterator over all neighbours (both in or out).""" |
|---|
| 1570 | for v in self.out_neighbours(): |
|---|
| 1571 | yield v |
|---|
| 1572 | for v in self.in_neighbours(): |
|---|
| 1573 | yield v |
|---|
| 1574 | Vertex.all_neighbours = _all_neighbours |
|---|
| 1575 | |
|---|
| 1576 | |
|---|
| 1577 | def _vertex_repr(self): |
|---|
| 1578 | if not self.is_valid(): |
|---|
| 1579 | return "<invalid Vertex object at 0x%x>" % (id(self)) |
|---|
| 1580 | return "<Vertex object with index '%d' at 0x%x>" % (int(self), id(self)) |
|---|
| 1581 | Vertex.__repr__ = _vertex_repr |
|---|
| 1582 | |
|---|
| 1583 | _edge_doc = """Edge descriptor. |
|---|
| 1584 | |
|---|
| 1585 | This class represents an edge in a :class:`~graph_tool.Graph`. |
|---|
| 1586 | |
|---|
| 1587 | :class:`~graph_tool.Edge` instances are hashable, and are convertible to a |
|---|
| 1588 | tuple, which contains the source and target vertices. |
|---|
| 1589 | """ |
|---|
| 1590 | |
|---|
| 1591 | |
|---|
| 1592 | def _edge_iter(self): |
|---|
| 1593 | """Iterate over the source and target""" |
|---|
| 1594 | for v in [self.source(), self.target()]: |
|---|
| 1595 | yield v |
|---|
| 1596 | |
|---|
| 1597 | |
|---|
| 1598 | def _edge_repr(self): |
|---|
| 1599 | if not self.is_valid(): |
|---|
| 1600 | return "<invalid Edge object at 0x%x>" % (id(self)) |
|---|
| 1601 | |
|---|
| 1602 | return ("<Edge object with source '%d' and target '%d'" + |
|---|
| 1603 | " at 0x%x>") % (int(self.source()), int(self.target()), id(self)) |
|---|
| 1604 | |
|---|
| 1605 | |
|---|
| 1606 | # There are several edge classes... me must cycle through them all to modify |
|---|
| 1607 | # them. |
|---|
| 1608 | def init_edge_classes(): |
|---|
| 1609 | for directed in [True, False]: |
|---|
| 1610 | for e_reversed in [True, False]: |
|---|
| 1611 | for e_filtered in [True, False]: |
|---|
| 1612 | for v_filtered in [True, False]: |
|---|
| 1613 | g = Graph(directed=directed) |
|---|
| 1614 | g.set_reversed(e_reversed) |
|---|
| 1615 | v = g.add_vertex() |
|---|
| 1616 | g.add_edge(v, v) |
|---|
| 1617 | if e_filtered: |
|---|
| 1618 | e_filter = g.new_edge_property("bool") |
|---|
| 1619 | e_filter.a = [1] |
|---|
| 1620 | g.set_edge_filter(e_filter) |
|---|
| 1621 | if v_filtered: |
|---|
| 1622 | v_filter = g.new_vertex_property("bool") |
|---|
| 1623 | v_filter.a = [1] |
|---|
| 1624 | g.set_vertex_filter(v_filter) |
|---|
| 1625 | e = g.edges().next() |
|---|
| 1626 | e.__class__.__repr__ = _edge_repr |
|---|
| 1627 | e.__class__.__iter__ = _edge_iter |
|---|
| 1628 | e.__class__.__doc__ = _edge_doc |
|---|
| 1629 | |
|---|
| 1630 | init_edge_classes() |
|---|
| 1631 | |
|---|
| 1632 | # Add convenience function to vector classes |
|---|
| 1633 | |
|---|
| 1634 | |
|---|
| 1635 | def _get_array_view(self): |
|---|
| 1636 | return self.get_array()[:] |
|---|
| 1637 | |
|---|
| 1638 | |
|---|
| 1639 | def _set_array_view(self, v): |
|---|
| 1640 | self.get_array()[:] = v |
|---|
| 1641 | |
|---|
| 1642 | vector_types = [Vector_bool, Vector_int32_t, Vector_int64_t, Vector_double, |
|---|
| 1643 | Vector_long_double] |
|---|
| 1644 | for vt in vector_types: |
|---|
| 1645 | vt.a = property(_get_array_view, _set_array_view, |
|---|
| 1646 | doc=r"""Shortcut to the `get_array` method as an attribute.""") |
|---|
| 1647 | Vector_string.a = None |
|---|
| 1648 | Vector_string.get_array = lambda self: None |
|---|
| 1649 | |
|---|
| 1650 | |
|---|
| 1651 | class GraphView(Graph): |
|---|
| 1652 | """ |
|---|
| 1653 | A view of selected vertices or edges of another graph. |
|---|
| 1654 | |
|---|
| 1655 | This class uses shared data from another :class:`~graph_tool.Graph` |
|---|
| 1656 | instance, but allows for local filtering of vertices and/or edges, edge |
|---|
| 1657 | directionality or reversal. See :ref:`sec_graph_views` for more details and |
|---|
| 1658 | examples. |
|---|
| 1659 | |
|---|
| 1660 | The existence of a :class:`~graph_tool.GraphView` object does not affect the |
|---|
| 1661 | original graph, except if the graph view is modified (addition or removal of |
|---|
| 1662 | vertices or edges), in which case the modification is directly reflected in |
|---|
| 1663 | the original graph (and vice-versa), since they both point to the same |
|---|
| 1664 | underlying data. Because of this, instances of |
|---|
| 1665 | :class:`~graph_tool.PropertyMap` can be used interchangeably with a graph |
|---|
| 1666 | and its views. |
|---|
| 1667 | |
|---|
| 1668 | The argument ``g`` must be an instance of a :class:`~graph_tool.Graph` |
|---|
| 1669 | class. If specified, ``vfilt`` and ``efilt`` select which vertices and edges |
|---|
| 1670 | are filtered, respectively. These parameters can either be a |
|---|
| 1671 | boolean-valued :class:`~graph_tool.PropertyMap` or a |
|---|
| 1672 | :class:`~numpy.ndarray`, which specify which vertices/edges are selected, or |
|---|
| 1673 | an unary function which returns ``True`` if a given vertex/edge is to be |
|---|
| 1674 | selected, or ``False`` otherwise. |
|---|
| 1675 | |
|---|
| 1676 | The boolean parameter ``directed`` can be used to set the directionality of |
|---|
| 1677 | the graph view. If ``directed = None``, the directionality is inherited from |
|---|
| 1678 | ``g``. |
|---|
| 1679 | |
|---|
| 1680 | If ``reversed = True``, the direction of the edges is reversed. |
|---|
| 1681 | |
|---|
| 1682 | If ``vfilt`` or ``efilt`` is anything other than a |
|---|
| 1683 | :class:`~graph_tool.PropertyMap` instance, the instantiation running time is |
|---|
| 1684 | :math:`O(V)` and :math:`O(E)`, respectively. Otherwise, the running time is |
|---|
| 1685 | :math:`O(1)`. |
|---|
| 1686 | """ |
|---|
| 1687 | |
|---|
| 1688 | def __init__(self, g, vfilt=None, efilt=None, directed=None, |
|---|
| 1689 | reversed=False): |
|---|
| 1690 | self.__base = g if not isinstance(g, GraphView) else g.base |
|---|
| 1691 | Graph.__init__(self) |
|---|
| 1692 | # copy graph reference |
|---|
| 1693 | self._Graph__graph = libcore.GraphInterface(g._Graph__graph, True) |
|---|
| 1694 | self._Graph__properties = g._Graph__properties |
|---|
| 1695 | self._Graph__known_properties = g._Graph__known_properties |
|---|
| 1696 | |
|---|
| 1697 | # set already existent filters |
|---|
| 1698 | vf = g.get_vertex_filter() |
|---|
| 1699 | if vf[0] is not None: |
|---|
| 1700 | self.set_vertex_filter(vf[0], vf[1]) |
|---|
| 1701 | ef = g.get_edge_filter() |
|---|
| 1702 | if ef[0] is not None: |
|---|
| 1703 | self.set_edge_filter(ef[0], ef[1]) |
|---|
| 1704 | |
|---|
| 1705 | if vfilt is not None: |
|---|
| 1706 | if type(vfilt) is PropertyMap: |
|---|
| 1707 | self.set_vertex_filter(vfilt) |
|---|
| 1708 | else: |
|---|
| 1709 | vmap = self.new_vertex_property("bool") |
|---|
| 1710 | if issubclass(type(vfilt), numpy.ndarray): |
|---|
| 1711 | vmap.a = vfilt |
|---|
| 1712 | else: |
|---|
| 1713 | omap, inv = g.get_vertex_filter() |
|---|
| 1714 | if omap is not None: |
|---|
| 1715 | vmap.a = omap.a if not inv else omap.a ^ 1 |
|---|
| 1716 | for v in g.vertices(): |
|---|
| 1717 | vmap[v] = vfilt(v) |
|---|
| 1718 | self.set_vertex_filter(vmap) |
|---|
| 1719 | |
|---|
| 1720 | if efilt is not None: |
|---|
| 1721 | if type(efilt) is PropertyMap: |
|---|
| 1722 | self.set_edge_filter(efilt) |
|---|
| 1723 | else: |
|---|
| 1724 | emap = self.new_edge_property("bool") |
|---|
| 1725 | if issubclass(type(efilt), numpy.ndarray): |
|---|
| 1726 | vmap.a = efilt |
|---|
| 1727 | else: |
|---|
| 1728 | omap, inv = g.get_edge_filter() |
|---|
| 1729 | if omap is not None: |
|---|
| 1730 | emap.a = omap.a if not inv else omap.a ^ 1 |
|---|
| 1731 | for e in g.edges(): |
|---|
| 1732 | emap[e] = efilt(e) |
|---|
| 1733 | self.set_edge_filter(emap) |
|---|
| 1734 | |
|---|
| 1735 | if directed is not None: |
|---|
| 1736 | self.set_directed(directed) |
|---|
| 1737 | if reversed: |
|---|
| 1738 | self.set_reversed(not g.is_reversed()) |
|---|
| 1739 | |
|---|
| 1740 | def __get_base(self): |
|---|
| 1741 | return self.__base |
|---|
| 1742 | base = property(__get_base, doc="Base graph.") |
|---|