Commit 7c3a1e99 authored by Tiago Peixoto's avatar Tiago Peixoto
Browse files

Implement PropertyArray to encapsulate property map data

The arrays returned by PropertyMap.get_array() are now PropertyArray
instances, which keep track of the PropertyMap data, and are able to
detect when it changes, and raise a ValueError exception. As such, it
becomes impossible to read or write to non-allocated memory if an array
becomes invalid.
parent f8093386
...@@ -62,6 +62,7 @@ Summary ...@@ -62,6 +62,7 @@ Summary
Vertex Vertex
Edge Edge
PropertyMap PropertyMap
PropertyArray
load_graph load_graph
group_vector_property group_vector_property
ungroup_vector_property ungroup_vector_property
...@@ -86,11 +87,12 @@ import scipy.stats ...@@ -86,11 +87,12 @@ import scipy.stats
from . core import __version__, Graph, Vector_bool, Vector_int32_t, \ from . core import __version__, Graph, Vector_bool, Vector_int32_t, \
Vector_int64_t, Vector_double, Vector_long_double, Vector_string, \ Vector_int64_t, Vector_double, Vector_long_double, Vector_string, \
value_types, load_graph, PropertyMap, Vertex, Edge, \ value_types, load_graph, PropertyArray, PropertyMap, Vertex, Edge, \
group_vector_property, ungroup_vector_property, show_config group_vector_property, ungroup_vector_property, show_config
__all__ = ["Graph", "Vertex", "Edge", "Vector_bool", "Vector_int32_t", __all__ = ["Graph", "Vertex", "Edge", "Vector_bool", "Vector_int32_t",
"Vector_int64_t", "Vector_double", "Vector_long_double", "Vector_int64_t", "Vector_double", "Vector_long_double",
"Vector_string", "value_types", "load_graph", "PropertyMap", "Vector_string", "value_types", "load_graph", "PropertyArray",
"group_vector_property", "ungroup_vector_property", "show_config", "PropertyMap", "group_vector_property", "ungroup_vector_property",
"__author__", "__copyright__", "__URL__", "__version__"] "show_config", "__author__", "__copyright__", "__URL__",
"__version__"]
...@@ -31,6 +31,8 @@ import os, os.path, re, struct, fcntl, termios, gzip, bz2, string,\ ...@@ -31,6 +31,8 @@ import os, os.path, re, struct, fcntl, termios, gzip, bz2, string,\
functools, types, weakref, copy functools, types, weakref, copy
from StringIO import StringIO from StringIO import StringIO
from decorators import _wraps, _require, _attrs, _limit_args from decorators import _wraps, _require, _attrs, _limit_args
from inspect import ismethod
import numpy
################################################################################ ################################################################################
# Utility functions # Utility functions
...@@ -110,10 +112,106 @@ def show_config(): ...@@ -110,10 +112,106 @@ def show_config():
################################################################################ ################################################################################
class PropertyMap(object): class PropertyArray(numpy.ndarray):
"""Property map class. """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."""
__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
This class provides a mapping from vertices, edges or whole graphs to # check if data really belongs to property map
if (prop_map._get_data().__array_interface__['data'][0] !=
obj._get_base_data()):
obj.prop_map = None
obj = numpy.asarray(obj)
return obj
def _get_base(self):
base = self
while base.base is not None:
base = base.base
return base
def _get_base_data(self):
return self._get_base().__array_interface__['data'][0]
def _check_data(self):
if self.prop_map is None:
return
data = self.prop_map._get_data()
if (data is None or
data.__array_interface__['data'][0] != self._get_base_data()):
raise ValueError(("The graph correspondig to the underlying" +
" property map %s has changed. The" +
" PropertyArray at 0x%x is no longer valid!") %
(repr(self.prop_map), id(self)))
def __array_finalize__(self, obj):
if type(obj) is PropertyArray:
obj._check_data()
if obj is not None:
# inherit prop_map only if the data is the same
if (type(obj) is PropertyArray and
self._get_base_data() == obj._get_base_data()):
self.prop_map = getattr(obj, 'prop_map', None)
else:
self.prop_map = None
self._check_data()
def __array_prepare__(self, out_arr, context=None):
self._check_data()
return numpy.ndarray.__array_prepare__(self, out_arr, context)
def __array_wrap__(self, out_arr, context=None):
#demote to ndarray
obj = numpy.ndarray.__array_wrap__(self, out_arr, context)
return numpy.asarray(obj)
# Overload members and operators to add data checking
def _wrap_method(method):
method = getattr(numpy.ndarray, method)
def checked_method(self, *args, **kwargs):
self._check_data()
return method(self, *args, **kwargs)
if ismethod(method):
checked_method = _wraps(method)(checked_method)
checked_method.__doc__ = getattr(method, "__doc__", None)
return checked_method
for method in ['all', 'any', 'argmax', 'argmin', 'argsort', 'astype',
'byteswap', 'choose', 'clip', 'compress', 'conj',
'conjugate', 'copy', 'cumprod', 'cumsum', 'diagonal', 'dot',
'dump', 'dumps', 'fill', 'flat', 'flatten', 'getfield',
'imag', 'item', 'itemset', 'itemsize', 'max', 'mean', 'min',
'newbyteorder', 'nonzero', 'prod', 'ptp', 'put', 'ravel',
'real', 'repeat', 'reshape', 'resize', 'round',
'searchsorted', 'setfield', 'setflags', 'sort', 'squeeze',
'std', 'sum', 'swapaxes', 'take', 'tofile', 'tolist',
'tostring', 'trace', 'transpose', 'var', 'view',
'__getitem__']:
locals()[method] = _wrap_method(method)
class PropertyMap(object):
"""This class provides a mapping from vertices, edges or whole graphs to
arbitrary properties. arbitrary properties.
The possible property types are listed below. The possible property types are listed below.
...@@ -124,18 +222,18 @@ class PropertyMap(object): ...@@ -124,18 +222,18 @@ class PropertyMap(object):
Type name Alias Type name Alias
======================= ================ ======================= ================
``bool`` ``uint8_t`` ``bool`` ``uint8_t``
``int32_t`` ``int`` ``int32_t`` ``int``
``int64_t`` ``long`` ``int64_t`` ``long``
``double`` ``float`` ``double`` ``float``
``long double`` ``long double``
``string`` ``string``
``vector<bool>`` ``vector<uint8_t>`` ``vector<bool>`` ``vector<uint8_t>``
``vector<int32_t>`` ``vector<int>`` ``vector<int32_t>`` ``vector<int>``
``vector<int64_t>`` ``vector<long>`` ``vector<int64_t>`` ``vector<long>``
``vector<double>`` ``vector<float>`` ``vector<double>`` ``vector<float>``
``vector<long double>`` ``vector<long double>``
``vector<string>`` ``vector<string>``
``python::object`` ``object`` ``python::object`` ``object``
======================= ================ ======================= ================
""" """
def __init__(self, pmap, g, key_type, key_trans=None): def __init__(self, pmap, g, key_type, key_trans=None):
...@@ -196,17 +294,22 @@ class PropertyMap(object): ...@@ -196,17 +294,22 @@ class PropertyMap(object):
return self.__map.value_type() return self.__map.value_type()
def get_array(self): def get_array(self):
"""Get an array with property values. """Get a :class:`~graph_tool.PropertyArray` with the property values.
.. WARNING:: .. WARNING::
The returned array does not own the data, which belongs to the The returned array does not own the data, which belongs to the
property map. Therefore, the returned array cannot have a longer property map. Therefore, if the graph changes, the array may become
lifespan than the property map itself! Furthermore, if the graph *invalid* and any operation on it will fail with a
changes, it may leave the pointer to the data in the array dangling! :class:`ValueError` exception. Do **not** store the array if
Do *not* store the array if the graph is to be modified, or the the graph is to be modified; store a **copy** instead.
original property map deleted; *store a copy instead*!
""" """
a = self._get_data()
return PropertyArray(a, prop_map=self)
def _get_data(self):
if self.__g() is None:
return None
self.__g().stash_filter(edge=True, vertex=True) self.__g().stash_filter(edge=True, vertex=True)
if self.__key_type == 'v': if self.__key_type == 'v':
n = self.__g().num_vertices() n = self.__g().num_vertices()
...@@ -215,7 +318,8 @@ class PropertyMap(object): ...@@ -215,7 +318,8 @@ class PropertyMap(object):
else: else:
n = 1 n = 1
self.__g().pop_filter(edge=True, vertex=True) self.__g().pop_filter(edge=True, vertex=True)
return self.__map.get_array(n) a = self.__map.get_array(n)
return a
def __get_array(self): def __get_array(self):
if self.get_array() != None: if self.get_array() != None:
...@@ -229,7 +333,7 @@ class PropertyMap(object): ...@@ -229,7 +333,7 @@ class PropertyMap(object):
a = property(__get_array, __set_array, a = property(__get_array, __set_array,
doc=r"""Shortcut to the :meth:`~PropertyMap.get_array` method doc=r"""Shortcut to the :meth:`~PropertyMap.get_array` method
as a property. A view to the array is returned, instead of the as a property. A view to the array is returned, instead of the
array, for convenience.""") array itself, for convenience.""")
def is_writable(self): def is_writable(self):
"""Return True if the property is writable.""" """Return True if the property is writable."""
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment