source: src/graph_tool/__init__.py @ 479daa

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