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