source: src/graph_tool/__init__.py @ 510c25

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