__init__.py 143 KB
Newer Older
1
#! /usr/bin/env python
2
# -*- coding: utf-8 -*-
3
#
4
5
# graph_tool -- a general graph manipulation python module
#
Tiago Peixoto's avatar
Tiago Peixoto committed
6
# Copyright (C) 2006-2020 Tiago de Paula Peixoto <tiago@skewed.de>
7
#
8
9
10
11
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option) any
# later version.
12
#
13
14
15
16
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
17
#
18
19
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
20

21
"""
Tiago Peixoto's avatar
Tiago Peixoto committed
22
23
24
25
26
27
28
29
30
31
32
33
34
graph_tool - efficient graph analysis and manipulation
======================================================

Summary
-------

.. autosummary::
   :nosignatures:

   Graph
   GraphView
   Vertex
   Edge
35
36
37
   VertexPropertyMap
   EdgePropertyMap
   GraphPropertyMap
Tiago Peixoto's avatar
Tiago Peixoto committed
38
39
40
   PropertyMap
   PropertyArray
   load_graph
41
   load_graph_from_csv
Tiago Peixoto's avatar
Tiago Peixoto committed
42
43
   group_vector_property
   ungroup_vector_property
44
   map_property_values
45
   infect_vertex_property
46
   edge_endpoint_property
47
   incident_edges_op
Tiago Peixoto's avatar
Tiago Peixoto committed
48
   perfect_prop_hash
Tiago Peixoto's avatar
Tiago Peixoto committed
49
   value_types
50
51
52
53
54
   openmp_enabled
   openmp_get_num_threads
   openmp_set_num_threads
   openmp_get_schedule
   openmp_set_schedule
Tiago Peixoto's avatar
Tiago Peixoto committed
55
56
   show_config

Tiago Peixoto's avatar
Tiago Peixoto committed
57
58

This module provides:
59

60
   1. A :class:`~graph_tool.Graph` class for graph representation and manipulation
61
62
   2. Property maps for Vertex, Edge or Graph.
   3. Fast algorithms implemented in C++.
63

64
65
How to use the documentation
----------------------------
66

67
68
Documentation is available in two forms: docstrings provided
with the code, and the full documentation available in
Tiago Peixoto's avatar
Tiago Peixoto committed
69
`the graph-tool homepage <http://graph-tool.skewed.de>`_.
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87

We recommend exploring the docstrings using `IPython
<http://ipython.scipy.org>`_, an advanced Python shell with TAB-completion and
introspection capabilities.

The docstring examples assume that ``graph_tool.all`` has been imported as
``gt``::

   >>> import graph_tool.all as gt

Code snippets are indicated by three greater-than signs::

   >>> x = x + 1

Use the built-in ``help`` function to view a function's docstring::

   >>> help(gt.Graph)

Tiago Peixoto's avatar
Tiago Peixoto committed
88
89
Contents
--------
90
"""
91

Tiago Peixoto's avatar
Tiago Peixoto committed
92
__author__ = "Tiago de Paula Peixoto <tiago@skewed.de>"
93
__copyright__ = "Copyright 2006-2020 Tiago de Paula Peixoto"
Tiago Peixoto's avatar
Tiago Peixoto committed
94
__license__ = "GPL version 3 or above"
Tiago Peixoto's avatar
Tiago Peixoto committed
95
__URL__ = "http://graph-tool.skewed.de"
96

97
98
99
100
# import numpy and scipy before everything to avoid weird segmentation faults
# depending on the order things are imported.

import numpy
101
import numpy.ma
102
103
import scipy

104
105
106

from .dl_import import *
dl_import("from . import libgraph_tool_core as libcore")
Tiago Peixoto's avatar
Tiago Peixoto committed
107
108
__version__ = libcore.mod_info().version

Tiago Peixoto's avatar
Tiago Peixoto committed
109
from . import gt_io  # sets up libcore io routines
Tiago Peixoto's avatar
Tiago Peixoto committed
110
111
112
113
114

import sys
import os
import re
import gzip
115
116
117
118
119
import bz2
try:
    import lzma
except ImportError:
    pass
120
121
122
123
try:
    import zstandard
except ImportError:
    pass
Tiago Peixoto's avatar
Tiago Peixoto committed
124
125
import weakref
import copy
126
import textwrap
Tiago Peixoto's avatar
Tiago Peixoto committed
127
import io
128
import collections.abc
129
130
import itertools
import csv
131
import contextlib
Tiago Peixoto's avatar
Tiago Peixoto committed
132

Alex Henrie's avatar
Alex Henrie committed
133
from .decorators import _require, _limit_args, _copy_func
Tiago Peixoto's avatar
Tiago Peixoto committed
134

135
136
__all__ = ["Graph", "GraphView", "Vertex", "Edge", "VertexBase", "EdgeBase",
           "Vector_bool", "Vector_int16_t", "Vector_int32_t", "Vector_int64_t",
137
           "Vector_double", "Vector_long_double", "Vector_string",
Tiago Peixoto's avatar
Tiago Peixoto committed
138
139
140
141
142
143
144
145
           "Vector_size_t", "Vector_cdouble", "value_types", "load_graph",
           "load_graph_from_csv", "VertexPropertyMap", "EdgePropertyMap",
           "GraphPropertyMap", "PropertyMap", "PropertyArray",
           "group_vector_property", "ungroup_vector_property",
           "map_property_values", "infect_vertex_property",
           "edge_endpoint_property", "incident_edges_op", "perfect_prop_hash",
           "seed_rng", "show_config", "openmp_enabled",
           "openmp_get_num_threads", "openmp_set_num_threads",
146
147
           "openmp_get_schedule", "openmp_set_schedule", "__author__",
           "__copyright__", "__URL__", "__version__"]
Tiago Peixoto's avatar
Tiago Peixoto committed
148

Tiago Peixoto's avatar
Tiago Peixoto committed
149
150
# this is rather pointless, but it works around a sphinx bug
graph_tool = sys.modules[__name__]
Tiago Peixoto's avatar
Tiago Peixoto committed
151
152
153
154
155
156
157
158
159

################################################################################
# Utility functions
################################################################################


def _prop(t, g, prop):
    """Return either a property map, or an internal property map with a given
    name."""
160
    if isinstance(prop, str):
Tiago Peixoto's avatar
Tiago Peixoto committed
161
162
163
164
165
166
167
168
        try:
            pmap = g.properties[(t, prop)]
        except KeyError:
            raise KeyError("no internal %s property named: %s" %\
                           ("vertex" if t == "v" else \
                            ("edge" if t == "e" else "graph"), prop))
    else:
        pmap = prop
169
    if pmap is None:
Tiago Peixoto's avatar
Tiago Peixoto committed
170
        return libcore.any()
171
172
173
174
    if t != prop.key_type():
        names = {'e': 'edge', 'v': 'vertex', 'g': 'graph'}
        raise ValueError("Expected '%s' property map, got '%s'" %
                         (names[t], names[prop.key_type()]))
175
176
177
178
179
180
    u = pmap.get_graph()
    if u is None:
        raise ValueError("Received orphaned property map")
    if g.base is not u.base:
        raise ValueError("Received property map for graph %s (base: %s), expected: %s (base: %s)" %
                         (str(g), str(g.base), str(u), str(u.base)))
181
    return pmap._get_any()
Tiago Peixoto's avatar
Tiago Peixoto committed
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201


def _degree(g, name):
    """Retrieve the degree type from string, or returns the corresponding
    property map."""
    deg = name
    if name == "in-degree" or name == "in":
        deg = libcore.Degree.In
    elif name == "out-degree" or name == "out":
        deg = libcore.Degree.Out
    elif name == "total-degree" or name == "total":
        deg = libcore.Degree.Total
    else:
        deg = _prop("v", g, deg)
    return deg


def _type_alias(type_name):
    alias = {"int8_t": "bool",
             "boolean": "bool",
202
             "short": "int16_t",
Tiago Peixoto's avatar
Tiago Peixoto committed
203
             "int": "int32_t",
Tiago Peixoto's avatar
Tiago Peixoto committed
204
             "unsigned int": "int32_t",
Tiago Peixoto's avatar
Tiago Peixoto committed
205
206
             "long": "int64_t",
             "long long": "int64_t",
207
             "unsigned long": "int64_t",
Tiago Peixoto's avatar
Tiago Peixoto committed
208
209
210
211
             "object": "python::object",
             "float": "double"}
    if type_name in alias:
        return alias[type_name]
212
    if type_name in value_types():
213
        return type_name
Tiago Peixoto's avatar
Tiago Peixoto committed
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
    ma = re.compile(r"vector<(.*)>").match(type_name)
    if ma:
        t = ma.group(1)
        if t in alias:
            return "vector<%s>" % alias[t]
    raise ValueError("invalid property value type: " + type_name)


def _python_type(type_name):
    type_name = _type_alias(type_name)
    if "vector" in type_name:
        ma = re.compile(r"vector<(.*)>").match(type_name)
        t = ma.group(1)
        return list, _python_type(t)
    if "int" in type_name:
        return int
    if type_name == "bool":
        return bool
    if "double" in type_name:
        return float
234
    if type_name == "string":
Tiago Peixoto's avatar
Tiago Peixoto committed
235
236
237
        return str
    return object

238
def _gt_type(obj):
239
240
241
242
243
    if isinstance(obj, numpy.dtype):
        t = obj.type
    else:
        t = type(obj)
    if issubclass(t, (numpy.int16, numpy.uint16, numpy.int8, numpy.uint8)):
244
        return "int16_t"
245
    if issubclass(t, (int, numpy.int32, numpy.uint32)):
246
        return "int32_t"
247
    if issubclass(t, (numpy.longlong, numpy.uint64, numpy.int64)):
248
        return "int64_t"
249
    if issubclass(t, (float, numpy.float, numpy.float16, numpy.float32, numpy.float64)):
250
        return "double"
251
    if issubclass(t, numpy.float128):
252
        return "long double"
253
    if issubclass(t, str):
254
        return "string"
255
    if issubclass(t, bool):
256
        return "bool"
257
    if issubclass(t, (list, numpy.ndarray)):
258
259
260
        return "vector<%s>" % _gt_type(obj[0])
    return "object"

261
def _converter(val_type):
262
263
    # attempt to convert to a compatible python type. This is useful,
    # for instance, when dealing with numpy types.
264
    vtype = _python_type(val_type)
265
    if type(vtype) is tuple:
266
267
268
269
270
        def convert(val):
            return [vtype[1](x) for x in val]
    elif vtype is object:
        def convert(val):
            return val
271
    elif vtype is str:
272
        return str
273
274
275
276
    else:
        def convert(val):
            return vtype(val)
    return convert
277

Tiago Peixoto's avatar
Tiago Peixoto committed
278
279
280
def show_config():
    """Show ``graph_tool`` build configuration."""
    info = libcore.mod_info()
281
282
283
284
285
286
287
288
    print("version:", info.version)
    print("gcc version:", info.gcc_version)
    print("compilation flags:", info.cxxflags)
    print("install prefix:", info.install_prefix)
    print("python dir:", info.python_dir)
    print("graph filtering:", libcore.graph_filtering_enabled())
    print("openmp:", libcore.openmp_enabled())
    print("uname:", " ".join(os.uname()))
Tiago Peixoto's avatar
Tiago Peixoto committed
289

290
def terminal_size():
291
292
293
294
295
296
297
    try:
        import fcntl, termios, struct
        h, w, hp, wp = struct.unpack('HHHH',
            fcntl.ioctl(0, termios.TIOCGWINSZ,
            struct.pack('HHHH', 0, 0, 0, 0)))
    except IOError:
        w, h = 80, 100
298
299
    return w, h

300
301
302
303
304
try:
    libcore.mod_info("wrong")
except BaseException as e:
    ArgumentError = type(e)

Tiago Peixoto's avatar
Tiago Peixoto committed
305
306
307
308
309
################################################################################
# Property Maps
################################################################################

class PropertyMap(object):
310
    """This base class provides a mapping from vertices, edges or whole graphs to
311
    arbitrary properties.
Tiago Peixoto's avatar
Tiago Peixoto committed
312

313
314
315
    See :ref:`sec_property_maps` for more details.

    The possible property value types are listed below.
Tiago Peixoto's avatar
Tiago Peixoto committed
316
317
318
319
320
321
322

    .. table::

        =======================     ======================
         Type name                  Alias
        =======================     ======================
        ``bool``                    ``uint8_t``
323
        ``int16_t``                 ``short``
Tiago Peixoto's avatar
Tiago Peixoto committed
324
325
326
327
328
329
        ``int32_t``                 ``int``
        ``int64_t``                 ``long``, ``long long``
        ``double``                  ``float``
        ``long double``
        ``string``
        ``vector<bool>``            ``vector<uint8_t>``
330
        ``vector<int16_t>``         ``short``
Tiago Peixoto's avatar
Tiago Peixoto committed
331
332
333
334
335
336
337
338
        ``vector<int32_t>``         ``vector<int>``
        ``vector<int64_t>``         ``vector<long>``, ``vector<long long>``
        ``vector<double>``          ``vector<float>``
        ``vector<long double>``
        ``vector<string>``
        ``python::object``          ``object``
        =======================     ======================
    """
339
    def __init__(self, pmap, g, key_type):
340
341
        if type(self) == PropertyMap:
            raise Exception("PropertyMap cannot be instantiated directly")
Tiago Peixoto's avatar
Tiago Peixoto committed
342
343
        self.__map = pmap
        self.__g = weakref.ref(g)
344
345
346
        self.__base_g = weakref.ref(g.base)  # keep reference to the
                                             # base graph, in case the
                                             # graph view is deleted.
Tiago Peixoto's avatar
Tiago Peixoto committed
347
        self.__key_type = key_type
348
        self.__convert = _converter(self.value_type())
Tiago Peixoto's avatar
Tiago Peixoto committed
349
350
        self.__register_map()

351
352
353
354
355
356
357
358
359
360
361
362
    def _get_any(self):
        t = self.key_type()
        g = self.get_graph()
        if t == "v":
            N = g.num_vertices(True)
        elif t == "e":
            N = g.edge_index_range
        else:
            N = 1
        self.reserve(N)
        return self.__map.get_map()

Tiago Peixoto's avatar
Tiago Peixoto committed
363
    def __register_map(self):
364
365
        for g in [self.__g(), self.__base_g()]:
            if g is not None:
366
                g._Graph__known_properties[id(self)] = weakref.ref(self)
Tiago Peixoto's avatar
Tiago Peixoto committed
367
368

    def __unregister_map(self):
369
        for g in [self.__g(), self.__base_g()]:
370
            if g is not None and id(self) in g._Graph__known_properties:
371
                del g._Graph__known_properties[id(self)]
Tiago Peixoto's avatar
Tiago Peixoto committed
372
373

    def __del__(self):
374
375
        if type(self) != PropertyMap:
            self.__unregister_map()
Tiago Peixoto's avatar
Tiago Peixoto committed
376
377
378
379
380
381
382
383
384

    def __repr__(self):
        # provide some more useful information
        if self.key_type() == "e":
            k = "Edge"
        elif self.key_type() == "v":
            k = "Vertex"
        else:
            k = "Graph"
385
        g = self.get_graph()
386
        if g is None:
Tiago Peixoto's avatar
Tiago Peixoto committed
387
388
389
            g = "a non-existent graph"
        else:
            g = "Graph 0x%x" % id(g)
390
        return ("<%sPropertyMap object with value type '%s',"
391
                + " for %s, at 0x%x>") % (k, self.value_type(), g, id(self))
Tiago Peixoto's avatar
Tiago Peixoto committed
392

393
394
395
396
397
398
399
400
401
    def copy(self, value_type=None, full=True):
        """Return a copy of the property map. If ``value_type`` is specified, the value
        type is converted to the chosen type. If ``full == False``, in the case
        of filtered graphs only the unmasked values are copied (with the
        remaining ones taking the type-dependent default value).

        """
        return self.get_graph().copy_property(self, value_type=value_type,
                                              full=full)
402

403
404
405
    def __copy__(self):
        return self.copy()

406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
    def __deepcopy__(self, memo):
        if self.value_type() != "python::object":
            return self.copy()
        else:
            pmap = self.copy()
            g = self.get_graph()
            if self.key_type() == "g":
                iters = [g]
            elif self.key_type() == "v":
                iters = g.vertices()
            else:
                iters = g.edges()
            for v in iters:
                pmap[v] = copy.deepcopy(self[v], memo)
            return pmap

422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
    def coerce_type(self, full=True):
        """Return a copy of the property map with the most appropriate type, i.e. the
        simplest type necessary to accomodate all the values exactly. If ``full
        == False``, in the case of filtered graphs only the unmasked values are
        copied (with the remaining ones taking the type-dependent default
        value).
        """
        types = ["bool", "int16_t", "int32_t", "int64_t", "double",
                 "long double", "vector<bool>", "vector<int16_t>",
                 "vector<int32_t>", "vector<int64_t>", "vector<double>",
                 "vector<long double>", "string", "vector<string>"]
        for t in types:
            try:
                p = self.copy(value_type=t, full=full)
                if t == "bool":
                    a = p.a if full else p.fa
                    if p.a.max() > 1:
                        continue
                if p.copy(value_type=self.value_type(), full=full) == self:
                    break
            except (TypeError, ValueError, OverflowError):
                pass
        return p


Tiago Peixoto's avatar
Tiago Peixoto committed
447
448
    def get_graph(self):
        """Get the graph class to which the map refers."""
449
450
451
452
        g = self.__g()
        if g is None:
            g = self.__base_g()
        return g
Tiago Peixoto's avatar
Tiago Peixoto committed
453
454
455
456
457
458
459
460
461
462
463
464
465
466

    def key_type(self):
        """Return the key type of the map. Either 'g', 'v' or 'e'."""
        return self.__key_type

    def value_type(self):
        """Return the value type of the map."""
        return self.__map.value_type()

    def python_value_type(self):
        """Return the python-compatible value type of the map."""
        return _python_type(self.__map.value_type())

    def get_array(self):
467
468
        """Get a :class:`numpy.ndarray` subclass (:class:`~graph_tool.PropertyArray`)
        with the property values.
Tiago Peixoto's avatar
Tiago Peixoto committed
469

470
471
472
473
        .. note::

           An array is returned *only if* the value type of the property map is
           a scalar. For vector, string or object types, ``None`` is returned
474
475
476
           instead. For vector and string objects, indirect array access is
           provided via the :func:`~graph_tool.PropertyMap.get_2d_array()` and
           :func:`~graph_tool.PropertyMap.set_2d_array()` member functions.
477
478

        .. warning::
Tiago Peixoto's avatar
Tiago Peixoto committed
479
480
481

           The returned array does not own the data, which belongs to the
           property map. Therefore, if the graph changes, the array may become
482
483
484
           *invalid*. Do **not** store the array if the graph is to be modified;
           store a **copy** instead.

Tiago Peixoto's avatar
Tiago Peixoto committed
485
        """
486
        return self._get_data()
Tiago Peixoto's avatar
Tiago Peixoto committed
487
488

    def __set_array(self, v):
489
        a = self.get_array()
490
491
492
        if a is None:
            raise TypeError("cannot set property map values from array for" +
                            " property map of type: " + self.value_type())
493
        a[:] = v
Tiago Peixoto's avatar
Tiago Peixoto committed
494

495
    a = property(get_array, __set_array,
Tiago Peixoto's avatar
Tiago Peixoto committed
496
                 doc=r"""Shortcut to the :meth:`~PropertyMap.get_array` method
497
498
499
500
                 as an attribute. This makes assignments more convenient, e.g.:

                 >>> g = gt.Graph()
                 >>> g.add_vertex(10)
Tiago Peixoto's avatar
Tiago Peixoto committed
501
                 <...>
502
503
504
505
506
507
508
509
510
511
512
513
                 >>> prop = g.new_vertex_property("double")
                 >>> prop.a = np.random.random(10)           # Assignment from array
                 """)

    def __get_set_f_array(self, v=None, get=True):
        g = self.get_graph()
        if g is None:
            return None
        a = self.get_array()
        filt = [None]
        if self.__key_type == 'v':
            filt = g.get_vertex_filter()
514
            N = g.num_vertices()
515
516
        elif self.__key_type == 'e':
            filt = g.get_edge_filter()
517
518
            if g.get_vertex_filter()[0] is not None:
                filt = (g.new_edge_property("bool"), filt[1])
519
                libcore.mark_edges(g._Graph__graph, _prop("e", g, filt[0]))
520
                if filt[1]:
521
                    filt[0].a = numpy.logical_not(filt[0].a)
522
            elif g.edge_index_range != g.num_edges():
523
                filt = (g.new_edge_property("bool"), False)
524
                libcore.mark_edges(g._Graph__graph, _prop("e", g, filt[0]))
525
            if filt[0] is None:
526
                N = g.edge_index_range
527
528
            else:
                N = (filt[0].a == (not filt[1])).sum()
529
        if get:
530
531
532
533
            if a is None:
                return a
            if filt[0] is None:
                return a
534
            return a[filt[0].a == (not filt[1])][:N]
535
        else:
536
            if a is None:
537
538
                raise TypeError("cannot set property map values from array for" +
                                " property map of type: " + self.value_type())
539
            if filt[0] is None:
540
541
542
543
                try:
                    a[:] = v
                except ValueError:
                    a[:] = v[:len(a)]
544
            else:
545
                m = filt[0].a == (not filt[1])
546
                m *= m.cumsum() <= N
547
548
549
550
                try:
                    a[m] = v
                except ValueError:
                    a[m] = v[:len(m)][m]
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574

    fa = property(__get_set_f_array,
                  lambda self, v: self.__get_set_f_array(v, False),
                  doc=r"""The same as the :attr:`~PropertyMap.a` attribute, but
                  instead an *indexed* array is returned, which contains only
                  entries for vertices/edges which are not filtered out. If
                  there are no filters in place, the array is not indexed, and
                  is identical to the :attr:`~PropertyMap.a` attribute.

                  Note that because advanced indexing is triggered, a **copy**
                  of the array is returned, not a view, as for the
                  :attr:`~PropertyMap.a` attribute. Nevertheless, the assignment
                  of values to the *whole* array at once works as expected.""")

    def __get_set_m_array(self, v=None, get=True):
        g = self.get_graph()
        if g is None:
            return None
        a = self.get_array()
        filt = [None]
        if self.__key_type == 'v':
            filt = g.get_vertex_filter()
        elif self.__key_type == 'e':
            filt = g.get_edge_filter()
575
576
            if g.get_vertex_filter()[0] is not None:
                filt = (g.new_edge_property("bool"), filt[1])
577
                libcore.mark_edges(g._Graph__graph, _prop("e", g, filt[0]))
578
579
                if filt[1]:
                    filt[0].a = 1 - filt[0].a
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
        if filt[0] is None or a is None:
            if get:
                return a
            else:
                return
        ma = numpy.ma.array(a, mask=(filt[0].a == False) if not filt[1] else (filt[0].a == True))
        if get:
            return ma
        else:
            ma[:] = v

    ma = property(__get_set_m_array,
                  lambda self, v: self.__get_set_m_array(v, False),
                  doc=r"""The same as the :attr:`~PropertyMap.a` attribute, but
                  instead a :class:`~numpy.ma.MaskedArray` object is returned,
                  which contains only entries for vertices/edges which are not
                  filtered out. If there are no filters in place, a regular
597
                  :class:`~graph_tool.PropertyArray` is returned, which
598
                  is identical to the :attr:`~PropertyMap.a` attribute.""")
Tiago Peixoto's avatar
Tiago Peixoto committed
599

600
    def get_2d_array(self, pos):
601
602
603
604
605
606
        r"""Return a two-dimensional array of shape ``(M,N)``, where ``N`` is the number
        of vertices or edges, and ``M`` is the size of each property vector,
        with contains a copy of all entries of the vector-valued property map.
        The parameter ``pos`` must be a sequence of integers which specifies the
        indexes of the property values which will be copied.
        """
607
608
609
610
611
612
613
614
615
616
617
618

        if self.key_type() == "g":
            raise ValueError("Cannot create multidimensional array for graph property maps.")
        if "vector" not in self.value_type() and (len(pos) > 1 or pos[0] != 0):
            raise ValueError("Cannot create array of dimension %d (indexes %s) from non-vector property map of type '%s'." \
                             % (len(pos), str(pos), self.value_type()))
        if "string" in self.value_type():
            if "vector" in self.value_type():
                p = ungroup_vector_property(self, pos)
            else:
                p = [self]
            g = self.get_graph()
619
620
            vfilt = g.get_vertex_filter()
            efilt = g.get_edge_filter()
621
622
623
            if self.key_type() == "v":
                iters = g.vertices()
            else:
624
625
626
627
628
629
                iters = [None for i in range(g.edge_index_range)]
                idx = g.edge_index
                for e in g.edges():
                    iters[idx[e]] = e
                iters = [e for e in iters if e is not None]
            a = [[] for i in range(len(p))]
630
631
            for v in iters:
                for i in range(len(p)):
632
                    a[i].append(p[i][v])
633
634
            a = numpy.array(a)
            return a
635

636
637
        p = ungroup_vector_property(self, pos)
        a = numpy.array([x.fa for x in p])
638
        return a
639
640

    def set_2d_array(self, a, pos=None):
641
642
643
644
645
646
647
        r"""Set the entries of the vector-valued property map from a two-dimensional
        array ``a`` of shape ``(M,N)``, where ``N`` is the number of vertices or
        edges, and ``M`` is the size of each property vector. If given, the
        parameter ``pos`` must be a sequence of integers which specifies the
        indexes of the property values which will be set (i.e. rows if the ``a``
        matrix).
        """
648
649
650
651
652
653
654
655
656
657
658
659
660
661

        if self.key_type() == "g":
            raise ValueError("Cannot set multidimensional array for graph property maps.")
        if "vector" not in self.value_type():
            if len(a.shape) != 1:
                raise ValueError("Cannot set array of shape %s to non-vector property map of type %s" % \
                                 (str(a.shape), self.value_type()))
            if self.value_type() != "string":
                self.fa = a
            else:
                g = self.get_graph()
                if self.key_type() == "v":
                    iters = g.vertices()
                else:
662
663
664
665
666
                    iters = [None for i in range(g.edge_index_range)]
                    idx = g.edge_index
                    for e in g.edges():
                        iters[idx[e]] = e
                    iters = [e for e in iters if e is not None]
667
668
                for j, v in enumerate(iters):
                    self[v] = a[j]
669
670
671
672
673
674
675
676
677
678
679
680
681
            return

        val = self.value_type()[7:-1]
        ps = []
        for i in range(a.shape[0]):
            ps.append(self.get_graph().new_property(self.key_type(), val))
            if self.value_type() != "string":
                ps[-1].fa = a[i]
            else:
                g = self.get_graph()
                if self.key_type() == "v":
                    iters = g.vertices()
                else:
682
683
684
685
686
                    iters = [None for i in range(g.edge_index_range)]
                    idx = g.edge_index
                    for e in g.edges():
                        iters[idx[e]] = e
                    iters = [e for e in iters if e is not None]
687
688
689
690
                for j, v in enumerate(iters):
                    ps[-1][v] = a[i, j]
        group_vector_property(ps, val, self, pos)

Tiago Peixoto's avatar
Tiago Peixoto committed
691
692
693
694
    def is_writable(self):
        """Return True if the property is writable."""
        return self.__map.is_writable()

695
696
697
698

    def set_value(self, val):
        """Sets all values in the property map to ``val``."""
        g = self.get_graph()
699
        val = self.__convert(val)
700
        if self.key_type() == "v":
701
            libcore.set_vertex_property(g._Graph__graph, _prop("v", g, self), val)
702
        elif self.key_type() == "e":
703
            libcore.set_edge_property(g._Graph__graph, _prop("e", g, self), val)
704
705
706
        else:
            self[g] = val

707
708
    def reserve(self, size):
        """Reserve enough space for ``size`` elements in underlying container. If the
709
           original size is already equal or larger, nothing will happen."""
710
711
        self.__map.reserve(size)

712
713
714
715
    def resize(self, size):
        """Resize the underlying container to contain exactly ``size`` elements."""
        self.__map.resize(size)

716
717
718
719
720
721
722
    def shrink_to_fit(self):
        """Shrink size of underlying container to accommodate only the necessary amount,
        and thus potentially freeing memory."""
        g = self.get_graph()
        if self.key_type() == "v":
            size = g.num_vertices(True)
        elif self.key_type() == "e":
723
            size = g.edge_index_range
724
725
726
727
728
        else:
            size = 1
        self.__map.resize(size)
        self.__map.shrink_to_fit()

Tiago Peixoto's avatar
Tiago Peixoto committed
729
730
731
732
733
734
735
736
    def swap(self, other):
        """Swap internal storage with ``other``."""
        if self.key_type() != other.key_type():
            raise ValueError("property maps must have the same key type")
        if self.value_type() != other.value_type():
            raise ValueError("property maps must have the same value type")
        self.__map.swap(other.__map)

737
738
739
740
    def data_ptr(self):
        """Return the pointer to memory where the data resides."""
        return self.__map.data_ptr()

741
742
    def __getstate__(self):
        g = self.get_graph()
743
744
        if g is None:
            raise ValueError("cannot pickle orphaned property map")
745
746
747
748
749
750
751
        value_type = self.value_type()
        key_type = self.key_type()
        if not self.is_writable():
            vals = None
        else:
            u = GraphView(g, skip_vfilt=True, skip_efilt=True)
            if key_type == "v":
752
                vals = [self.__convert(self[v]) for v in u.vertices()]
753
            elif key_type == "e":
754
                vals = [self.__convert(self[e]) for e in u.edges()]
755
            else:
756
                vals = self.__convert(self[g])
757
758
759
760
761
762
763
764
765
766

        state = dict(g=g, value_type=value_type,
                     key_type=key_type, vals=vals,
                     is_vindex=self is g.vertex_index,
                     is_eindex=self is g.edge_index)

        return state

    def __setstate__(self, state):
        g = state["g"]
767
768
        key_type = state["key_type"]
        value_type = state["value_type"]
769
770
771
772
773
774
775
776
777
        vals = state["vals"]

        if state["is_vindex"]:
            pmap = g.vertex_index
        elif state["is_eindex"]:
            pmap = g.edge_index
        else:
            u = GraphView(g, skip_vfilt=True, skip_efilt=True)
            if key_type == "v":
778
                pmap = u.new_vertex_property(value_type, vals=vals)
779
            elif key_type == "e":
780
                pmap = u.new_edge_property(value_type, vals=vals)
781
            else:
782
783
784
                pmap = u.new_graph_property(value_type)
                pmap[u] = vals
            pmap = g.own_property(pmap)
785
786
787
788
789

        self.__map = pmap.__map
        self.__g = pmap.__g
        self.__base_g = pmap.__base_g
        self.__key_type = key_type
Tiago Peixoto's avatar
Tiago Peixoto committed
790
        self.__convert = _converter(self.value_type())
791
792
        self.__register_map()

793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
    # Guarantee pickle backward compatibility

    def __getitem__(self, k):
        if self.key_type() == "v":
            return VertexPropertyMap.__getitem__(self, k)
        if self.key_type() == "e":
            return EdgePropertyMap.__getitem__(self, k)
        if self.key_type() == "g":
            return GraphPropertyMap.__getitem__(self, k)

    def __setitem__(self, k, v):
        if self.key_type() == "v":
            VertexPropertyMap.__setitem__(self, k, v)
        if self.key_type() == "e":
            EdgePropertyMap.__setitem__(self, k, v)
        if self.key_type() == "g":
            GraphPropertyMap.__setitem__(self, k, v)

    def __iter__(self):
        if self.key_type() == "v":
            return VertexPropertyMap.__iter__(self)
        if self.key_type() == "e":
            return EdgePropertyMap.__iter__(self)
        if self.key_type() == "g":
            return GraphPropertyMap.__iter__(self)

    def _get_data(self):
        if self.key_type() == "v":
821
            return VertexPropertyMap._get_data(self)
822
        if self.key_type() == "e":
823
            return EdgePropertyMap._get_data(self)
824
        if self.key_type() == "g":
825
            return GraphPropertyMap._get_data(self)
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862


class VertexPropertyMap(PropertyMap):
    """This class provides a mapping from vertices to arbitrary properties.

    See :ref:`sec_property_maps` and :class:`PropertyMap` for more details.

    """

    def __init__(self, pmap, g):
        PropertyMap.__init__(self, pmap, g, "v")

    def __getitem__(self, k):
        return self._PropertyMap__map[int(k)]

    def __setitem__(self, k, v):
        k = int(k)
        try:
            self._PropertyMap__map[k] = v
        except TypeError:
            self._PropertyMap__map[k] = self._PropertyMap__convert(v)

    def __iter__(self):
        g = self.get_graph()
        for v in g.vertices():
            yield self[v]

    def _get_data(self):
        g = self.get_graph()
        if g is None:
            raise ValueError("Cannot get array for an orphaned property map")
        n = g._Graph__graph.get_num_vertices(False)
        a = self._PropertyMap__map.get_array(n)
        if a is None:
            return None
        return PropertyArray(a, self)

863
    def __eq__(self, other):
864
865
        if not isinstance(other, VertexPropertyMap):
            return False
866
867
868
869
870
871
872
        g = self.get_graph()
        if g.base is not other.get_graph().base:
            return False
        return libcore.compare_vertex_properties(g._Graph__graph,
                                                 self._get_any(),
                                                 other._get_any())

873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
class EdgePropertyMap(PropertyMap):
    """This class provides a mapping from edges to arbitrary properties.

    See :ref:`sec_property_maps` and :class:`PropertyMap` for more details.

    """

    def __init__(self, pmap, g):
        PropertyMap.__init__(self, pmap, g, "e")

    def __getitem__(self, k):
        return self._PropertyMap__map[k]

    def __setitem__(self, k, v):
        try:
            self._PropertyMap__map[k] = v
        except TypeError:
890
            self._PropertyMap__map[k] = self._PropertyMap__convert(v)
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906

    def __iter__(self):
        g = self.get_graph()
        for e in g.edges():
            yield self[e]

    def _get_data(self):
        g = self.get_graph()
        if g is None:
            raise ValueError("Cannot get array for an orphaned property map")
        n = g.edge_index_range
        a = self._PropertyMap__map.get_array(n)
        if a is None:
            return None
        return PropertyArray(a, self)

907
    def __eq__(self, other):
908
909
        if not isinstance(other, EdgePropertyMap):
            return False
910
911
912
913
914
915
916
        g = self.get_graph()
        if g.base is not other.get_graph().base:
            return False
        return libcore.compare_edge_properties(g._Graph__graph,
                                               self._get_any(),
                                               other._get_any())

917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
class GraphPropertyMap(PropertyMap):
    """This class provides a mapping from graphs to arbitrary properties.

    See :ref:`sec_property_maps` and :class:`PropertyMap` for more details.

    """

    def __init__(self, pmap, g):
        PropertyMap.__init__(self, pmap, g, "g")

    def __getitem__(self, k):
        return self._PropertyMap__map[self.get_graph()._Graph__graph]

    def __setitem__(self, k, v):
        g = self.get_graph()._Graph__graph
        try:
            self._PropertyMap__map[g] = v
        except TypeError:
            self._PropertyMap__map[g] = self._PropertyMap__convert(v)

    def __iter__(self):
        return [self.get_graph()]

    def _get_data(self):
        g = self.get_graph()
        if g is None:
            raise ValueError("Cannot get array for an orphaned property map")
        a = self.__map.get_array(1)
        if a is None:
            return None
        return PropertyArray(a, self)


950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
class PropertyArray(numpy.ndarray):
    """This is a :class:`~numpy.ndarray` subclass which keeps a reference of its
    :class:`~graph_tool.PropertyMap` owner.
    """

    __array_priority__ = -10

    def _get_pmap(self):
        return self._prop_map

    def _set_pmap(self, value):
        self._prop_map = value

    prop_map = property(_get_pmap, _set_pmap,
                        doc=":class:`~graph_tool.PropertyMap` owner instance.")

    def __new__(cls, input_array, prop_map):
        obj = numpy.asarray(input_array).view(cls)
        obj.prop_map = prop_map
        return obj
Tiago Peixoto's avatar
Tiago Peixoto committed
970
971
972
973

def _check_prop_writable(prop, name=None):
    if not prop.is_writable():
        raise ValueError("property map%s is not writable." %\
Tiago Peixoto's avatar
Tiago Peixoto committed
974
                         ((" '%s'" % name) if name is not None else ""))
Tiago Peixoto's avatar
Tiago Peixoto committed
975
976
977


def _check_prop_scalar(prop, name=None, floating=False):
978
    scalars = ["bool", "int16_t", "int32_t", "int64_t", "unsigned long",
Tiago Peixoto's avatar
Tiago Peixoto committed
979
980
981
982
983
984
               "double", "long double"]
    if floating:
        scalars = ["double", "long double"]

    if prop.value_type() not in scalars:
        raise ValueError("property map%s is not of scalar%s type." %\
Tiago Peixoto's avatar
Tiago Peixoto committed
985
                         (((" '%s'" % name) if name is not None else ""),
Tiago Peixoto's avatar
Tiago Peixoto committed
986
987
988
989
                          (" floating" if floating else "")))


def _check_prop_vector(prop, name=None, scalar=True, floating=False):
990
    scalars = ["bool", "int16_t", "int32_t", "int64_t", "unsigned long",
Tiago Peixoto's avatar
Tiago Peixoto committed
991
992
993
994
995
996
997
998
               "double", "long double"]
    if not scalar:
        scalars += ["string"]
    if floating:
        scalars = ["double", "long double"]
    vals = ["vector<%s>" % v for v in scalars]
    if prop.value_type() not in vals:
        raise ValueError("property map%s is not of vector%s type." %\
Tiago Peixoto's avatar
Tiago Peixoto committed
999
                         (((" '%s'" % name) if name is not None else ""),
Tiago Peixoto's avatar
Tiago Peixoto committed
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
                          (" floating" if floating else "")))


def group_vector_property(props, value_type=None, vprop=None, pos=None):
    """Group list of properties ``props`` into a vector property map of the same type.

    Parameters
    ----------
    props : list of :class:`~graph_tool.PropertyMap`
        Properties to be grouped.
    value_type : string (optional, default: None)
        If supplied, defines the value type of the grouped property.
    vprop : :class:`~graph_tool.PropertyMap` (optional, default: None)
        If supplied, the properties are grouped into this property map.
    pos : list of ints (optional, default: None)
        If supplied, should contain a list of indexes where each corresponding
        element of ``props`` should be inserted.

    Returns
    -------
    vprop : :class:`~graph_tool.PropertyMap`
       A vector property map with the grouped values of each property map in
       ``props``.
1023
1024
1025
1026
1027
1028

    Examples
    --------
    >>> from numpy.random import seed, randint
    >>> from numpy import array
    >>> seed(42)
Tiago Peixoto's avatar
Tiago Peixoto committed
1029
    >>> gt.seed_rng(42)
1030
    >>> g = gt.random_graph(100, lambda: (3, 3))
1031
1032
    >>> props = [g.new_vertex_property("int") for i in range(3)]
    >>> for i in range(3):
1033
1034
    ...    props[i].a = randint(0, 100, g.num_vertices())
    >>> gprop = gt.group_vector_property(props)
1035
    >>> print(gprop[g.vertex(0)].a)
Tiago Peixoto's avatar
Tiago Peixoto committed
1036
    [51 25  8]
1037
    >>> print(array([p[g.vertex(0)] for p in props]))
Tiago Peixoto's avatar
Tiago Peixoto committed
1038
    [51 25  8]
Tiago Peixoto's avatar
Tiago Peixoto committed
1039
    """
1040

Tiago Peixoto's avatar
Tiago Peixoto committed
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
    g = props[0].get_graph()
    vtypes = set()
    keys = set()
    for i, p in enumerate(props):
        if "vector" in p.value_type():
            raise ValueError("property map 'props[%d]' is a vector property." %
                             i)
        vtypes.add(p.value_type())
        keys.add(p.key_type())
    if len(keys) > 1:
        raise ValueError("'props' must be of the same key type.")
    k = keys.pop()

Tiago Peixoto's avatar
Tiago Peixoto committed
1054
1055
    if vprop is None:
        if value_type is None and len(vtypes) == 1:
Tiago Peixoto's avatar
Tiago Peixoto committed
1056
1057
            value_type = vtypes.pop()

Tiago Peixoto's avatar
Tiago Peixoto committed
1058
        if value_type is not None:
Tiago Peixoto's avatar
Tiago Peixoto committed
1059
1060
1061
1062
1063
1064
1065
1066
            value_type = "vector<%s>" % value_type
            if k == 'v':
                vprop = g.new_vertex_property(value_type)
            elif k == 'e':
                vprop = g.new_edge_property(value_type)
            else:
                vprop = g.new_graph_property(value_type)
        else:
1067
1068
            raise ValueError("Can't automatically determine property map value" +
                             " type. Please provide the 'value_type' parameter.")
Tiago Peixoto's avatar
Tiago Peixoto committed
1069
1070
1071
1072
    _check_prop_vector(vprop, name="vprop", scalar=False)

    for i, p in enumerate(props):
        if k != "g":
1073
1074
            u = GraphView(g, directed=True, reversed=g.is_reversed(),
                          skip_properties=True)
1075
            libcore.group_vector_property(u._Graph__graph, _prop(k, g, vprop),
Tiago Peixoto's avatar
Tiago Peixoto committed
1076
                                          _prop(k, g, p),
Tiago Peixoto's avatar
Tiago Peixoto committed
1077
                                          i if pos is None else pos[i],
Tiago Peixoto's avatar
Tiago Peixoto committed
1078
1079
                                          k == 'e')
        else:
1080
            vprop[g][i if pos is None else pos[i]] = p[g]
Tiago Peixoto's avatar
Tiago Peixoto committed
1081
1082
1083
1084
1085
1086
    return vprop


def ungroup_vector_property(vprop, pos, props=None):
    """Ungroup vector property map ``vprop`` into a list of individual property maps.

1087
    Parameters
Tiago Peixoto's avatar
Tiago Peixoto committed
1088
1089
1090
    ----------
    vprop : :class:`~graph_tool.PropertyMap`
        Vector property map to be ungrouped.
1091
1092
1093
    pos : list of ints
        A list of indexes corresponding to where each element of ``vprop``
        should be inserted into the ungrouped list.
Tiago Peixoto's avatar
Tiago Peixoto committed
1094
1095
1096
1097
1098
1099
1100
1101
    props : list of :class:`~graph_tool.PropertyMap`  (optional, default: None)
        If supplied, should contain a list of property maps to which ``vprop``
        should be ungroupped.

    Returns
    -------
    props : list of :class:`~graph_tool.PropertyMap`
       A list of property maps with the ungrouped values of ``vprop``.
1102
1103
1104
1105
1106
1107

    Examples
    --------
    >>> from numpy.random import seed, randint
    >>> from numpy import array
    >>> seed(42)
Tiago Peixoto's avatar
Tiago Peixoto committed
1108
    >>> gt.seed_rng(42)
1109
1110
1111
1112
1113
    >>> g = gt.random_graph(100, lambda: (3, 3))
    >>> prop = g.new_vertex_property("vector<int>")
    >>> for v in g.vertices():
    ...    prop[v] = randint(0, 100, 3)
    >>> uprops = gt.ungroup_vector_property(prop, [0, 1, 2])
1114
    >>> print(prop[g.vertex(0)].a)
Tiago Peixoto's avatar
Tiago Peixoto committed
1115
    [51 92 14]
1116
    >>> print(array([p[g.vertex(0)] for p in uprops]))
Tiago Peixoto's avatar
Tiago Peixoto committed
1117
    [51 92 14]
Tiago Peixoto's avatar
Tiago Peixoto committed
1118
1119
1120
1121
1122
1123
    """

    g = vprop.get_graph()
    _check_prop_vector(vprop, name="vprop", scalar=False)
    k = vprop.key_type()
    value_type = vprop.value_type().split("<")[1].split(">")[0]
Tiago Peixoto's avatar
Tiago Peixoto committed
1124
    if props is None:
Tiago Peixoto's avatar
Tiago Peixoto committed
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
        if k == 'v':
            props = [g.new_vertex_property(value_type) for i in pos]
        elif k == 'e':
            props = [g.new_edge_property(value_type) for i in pos]
        else:
            props = [g.new_graph_property(value_type) for i in pos]

    for i, p in enumerate(pos):
        if props[i].key_type() != k:
            raise ValueError("'props' must be of the same key type as 'vprop'.")

        if k != 'g':
1137
1138
1139
            u = GraphView(g, directed=True, reversed=g.is_reversed(),
                          skip_properties=True)
            libcore.ungroup_vector_property(u._Graph__graph,
Tiago Peixoto's avatar
Tiago Peixoto committed
1140
1141
1142
1143
1144
1145
1146
1147
1148
                                            _prop(k, g, vprop),
                                            _prop(k, g, props[i]),
                                            p, k == 'e')
        else:
            if len(vprop[g]) <= pos[i]:
                vprop[g].resize(pos[i] + 1)
            props[i][g] = vprop[g][pos[i]]
    return props

1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
def map_property_values(src_prop, tgt_prop, map_func):
    """Map the values of ``src_prop`` to ``tgt_prop`` according to the mapping
    function ``map_func``.

    Parameters
    ----------
    src_prop : :class:`~graph_tool.PropertyMap`
        Source property map.
    tgt_prop : :class:`~graph_tool.PropertyMap`
        Target property map.
    map_func : function or callable object
        Function mapping values of ``src_prop`` to values of ``tgt_prop``.

    Returns
    -------
    None

    Examples
    --------
    >>> g = gt.collection.data["lesmis"]
    >>> label_len = g.new_vertex_property("int64_t")
    >>> gt.map_property_values(g.vp.label, label_len,
Tiago Peixoto's avatar
Tiago Peixoto committed
1171
    ...                        lambda x: len(x))
1172
    >>> print(label_len.a)
Tiago Peixoto's avatar
Tiago Peixoto committed
1173
1174
1175
1176
    [ 6  8 14 11 12  8 12  8  5  6  7  7 10  6  7  7  9  9  7 11  9  6  7  7
     13 10  7  6 12 10  8  8 11  6  5 12  6 10 11  9 12  7  7  6 14  7  9  9
      8 12  6 16 12 11 14  6  9  6  8 10  9  7 10  7  7  4  9 14  9  5 10 12
      9  6  6  6 12]
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
    """

    if src_prop.key_type() != tgt_prop.key_type():
        raise ValueError("src_prop and tgt_prop must be of the same key type")
    g = src_prop.get_graph()
    k = src_prop.key_type()
    if k == "g":
        tgt_prop[g] = map_func(src_prop[g])
        return
    u = GraphView(g, directed=True, reversed=g.is_reversed(),
                  skip_properties=True)
    libcore.property_map_values(u._Graph__graph,
                                _prop(k, g, src_prop),
                                _prop(k, g, tgt_prop),
                                map_func, k == 'e')
Tiago Peixoto's avatar
Tiago Peixoto committed
1192

1193
1194
def infect_vertex_property(g, prop, vals=None):
    """Propagate the `prop` values of vertices with value `val` to all their
1195
    out-neighbors.
1196
1197
1198

    Parameters
    ----------
1199
    prop : :class:`~graph_tool.VertexPropertyMap`
1200
1201
1202
1203
1204
1205
1206
        Property map to be modified.
    vals : list (optional, default: `None`)
        List of values to be propagated. If not provided, all values
        will be propagated.

    Returns
    -------
1207
    None : ``None``
1208
1209
1210
1211
1212

    Examples
    --------
    >>> from numpy.random import seed
    >>> seed(42)
Tiago Peixoto's avatar
Tiago Peixoto committed
1213
    >>> gt.seed_rng(42)
1214
    >>> g = gt.random_graph(100, lambda: (3, 3))
Tiago Peixoto's avatar
Tiago Peixoto committed
1215
    >>> prop = g.vertex_index.copy("int32_t")
1216
    >>> gt.infect_vertex_property(g, prop, [10])
1217
    >>> print(sum(prop.a == 10))
Tiago Peixoto's avatar
Tiago Peixoto committed
1218
    4
1219
1220
1221
1222
    """
    libcore.infect_vertex_property(g._Graph__graph, _prop("v", g, prop),
                                   vals)

Tiago Peixoto's avatar
Tiago Peixoto committed
1223

1224
1225
1226
1227
@_limit_args({"endpoint": ["source", "target"]})
def edge_endpoint_property(g, prop, endpoint, eprop=None):
    """Return an edge property map corresponding to the vertex property `prop` of
    either the target and source of the edge, according to `endpoint`.
Tiago Peixoto's avatar
Tiago Peixoto committed
1228
1229
1230

    Parameters
    ----------
1231
    prop : :class:`~graph_tool.VertexPropertyMap`
1232
1233
1234
1235
        Vertex property map to be used to propagated to the edge.
    endpoint : `"source"` or `"target"`
        Edge endpoint considered. If the graph is undirected, the source is
        always the vertex with the lowest index.
1236
    eprop : :class:`~graph_tool.EdgePropertyMap` (optional, default: `None`)
1237
        If provided, the resulting edge properties will be stored here.
Tiago Peixoto's avatar
Tiago Peixoto committed
1238
1239
1240

    Returns
    -------
1241
    eprop : :class:`~graph_tool.EdgePropertyMap`
1242
        Propagated edge property.
Tiago Peixoto's avatar
Tiago Peixoto committed
1243
1244
1245

    Examples
    --------
Tiago Peixoto's avatar
Tiago Peixoto committed
1246
    >>> gt.seed_rng(42)
Tiago Peixoto's avatar
Tiago Peixoto committed
1247
    >>> g = gt.random_graph(100, lambda: (3, 3))
1248
1249
    >>> esource = gt.edge_endpoint_property(g, g.vertex_index, "source")
    >>> print(esource.a)
Tiago Peixoto's avatar
Tiago Peixoto committed
1250
    [ 0  0  0 96 96 96 92 92 92 88 88 88 84 84 84 80 80 80 76 76 76 72 72 72
Tiago Peixoto's avatar
Tiago Peixoto committed
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
     68 68 68 64 64 64 60 60 60 56 56 56 52 52 52 48 48 48 44 44 44 40 40 40
     36 36 36 32 32 32 28 28 28 24 24 24 20 20 20 16 16 16 12 12 12  8  8  8
      4  4  4 99 99 99  1  1  1  2  2  2  3  3  3  5  5  5  6  6  6  7  7  7
      9  9  9 10 10 10 14 14 14 19 19 19 25 25 25 30 30 30 35 35 35 41 41 41
     46 46 46 51 51 51 57 57 57 62 62 62 67 67 67 73 73 73 78 78 78 83 83 83
     89 89 89 94 94 94 11 11 11 98 98 98 97 97 97 95 95 95 93 93 93 91 91 91
     90 90 90 87 87 87 86 86 86 85 85 85 82 82 82 81 81 81 79 79 79 77 77 77
     75 75 75 74 74 74 71 71 71 69 69 69 61 61 61 54 54 54 47 47 47 39 39 39
     33 33 33 26 26 26 18 18 18 70 70 70 13 13 13 15 15 15 17 17 17 21 21 21
     22 22 22 23 23 23 27 27 27 29 29 29 31 31 31 34 34 34 37 37 37 38 38 38
     42 42 42 43 43 43 45 45 45 49 49 49 50 50 50 53 53 53 55 55 55 58 58 58
     59 59 59 63 63 63 65 65 65 66 66 66]
Tiago Peixoto's avatar
Tiago Peixoto committed
1263
    """
1264

Tiago Peixoto's avatar
Tiago Peixoto committed
1265
    val_t = prop.value_type()
1266
    if val_t == "unsigned long" or val_t == "unsigned int":
1267
1268
1269
1270
1271
        val_t = "int64_t"
    if eprop is None:
        eprop = g.new_edge_property(val_t)
    if eprop.value_type() != val_t:
        raise ValueError("'eprop' must be of the same value type as 'prop': " +
Tiago Peixoto's avatar
Tiago Peixoto committed
1272
                         val_t)
1273
    libcore.edge_endpoint(g._Graph__graph, _prop("v", g, prop),
1274
                          _prop("e", g, eprop), endpoint)