source: src/graph_tool/__init__.py @ 211f67

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