source: src/graph_tool/__init__.py @ 49f2ec

Revision 49f2ec, 64.5 KB checked in by Tiago de Paula Peixoto <tiago@…>, 23 months ago (diff)
Diverse documentation improvements This better organizes the documentation of the Graph class and provides other small fixes.
  • Property mode set to 100644
Line 
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"""
22graph_tool - efficient graph analysis and manipulation
23======================================================
24
25Summary
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
44This 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
50How to use the documentation
51----------------------------
52
53Documentation is available in two forms: docstrings provided
54with the code, and the full documentation available in
55`the graph-tool homepage <http://graph-tool.skewed.de>`_.
56
57We recommend exploring the docstrings using `IPython
58<http://ipython.scipy.org>`_, an advanced Python shell with TAB-completion and
59introspection capabilities.
60
61The docstring examples assume that ``graph_tool.all`` has been imported as
62``gt``::
63
64   >>> import graph_tool.all as gt
65
66Code snippets are indicated by three greater-than signs::
67
68   >>> x = x + 1
69
70Use the built-in ``help`` function to view a function's docstring::
71
72   >>> help(gt.Graph)
73
74Contents
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
86import numpy
87import numpy.ma
88import scipy
89import scipy.stats
90
91from dl_import import *
92dl_import("import libgraph_tool_core as libcore")
93import libgraph_tool_core as libcore   # for pylint
94__version__ = libcore.mod_info().version
95
96import io  # sets up libcore io routines
97
98import sys
99import os
100import re
101import gzip
102import bz2
103import weakref
104import copy
105
106from StringIO import StringIO
107from decorators import _wraps, _require, _attrs, _limit_args
108from 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
118graph_tool = sys.modules[__name__]
119
120################################################################################
121# Utility functions
122################################################################################
123
124
125def _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
147def _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
162def _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
182def _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
199def _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
208def 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
225class 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
325class 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
550def _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
556def _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
568def _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
582def 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
663def 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
730class 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
757from 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
762class 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
1516def 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
1528def value_types():
1529    """Return a list of possible properties value types."""
1530    return libcore.get_property_types()
1531
1532# Vertex and Edge Types
1533# =====================
1534from libgraph_tool_core import Vertex, Edge
1535
1536Vertex.__doc__ = """Vertex descriptor.
1537
1538This class represents a vertex in a :class:`~graph_tool.Graph` instance.
1539
1540:class:`~graph_tool.Vertex` instances are hashable, and are convertible to
1541integers, corresponding to its index (see :attr:`~graph_tool.Graph.vertex_index`).
1542"""
1543
1544
1545def _out_neighbours(self):
1546    """Return an iterator over the out-neighbours."""
1547    for e in self.out_edges():
1548        yield e.target()
1549Vertex.out_neighbours = _out_neighbours
1550
1551
1552def _in_neighbours(self):
1553    """Return an iterator over the in-neighbours."""
1554    for e in self.in_edges():
1555        yield e.source()
1556Vertex.in_neighbours = _in_neighbours
1557
1558
1559def _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
1565Vertex.all_edges = _all_edges
1566
1567
1568def _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
1574Vertex.all_neighbours = _all_neighbours
1575
1576
1577def _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))
1581Vertex.__repr__ = _vertex_repr
1582
1583_edge_doc = """Edge descriptor.
1584
1585This class represents an edge in a :class:`~graph_tool.Graph`.
1586
1587:class:`~graph_tool.Edge` instances are hashable, and are convertible to a
1588tuple, which contains the source and target vertices.
1589"""
1590
1591
1592def _edge_iter(self):
1593    """Iterate over the source and target"""
1594    for v in [self.source(), self.target()]:
1595        yield v
1596
1597
1598def _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.
1608def 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
1630init_edge_classes()
1631
1632# Add convenience function to vector classes
1633
1634
1635def _get_array_view(self):
1636    return self.get_array()[:]
1637
1638
1639def _set_array_view(self, v):
1640    self.get_array()[:] = v
1641
1642vector_types = [Vector_bool, Vector_int32_t, Vector_int64_t, Vector_double,
1643                Vector_long_double]
1644for 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.""")
1647Vector_string.a = None
1648Vector_string.get_array = lambda self: None
1649
1650
1651class 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.")
Note: See TracBrowser for help on using the repository browser.