Commit bd96105e authored by Tiago Peixoto's avatar Tiago Peixoto
Browse files

Include support for removal of vertex sequences

This fixes #189
parent 0bc62ada
...@@ -143,6 +143,22 @@ descriptors: ...@@ -143,6 +143,22 @@ descriptors:
>>> print(len(list(vlist))) >>> print(len(list(vlist)))
10 10
Each vertex in a graph has an unique index, which is *always* between
:math:``0`` and :math:``N-1``, where :math:``N`` is the number of
vertices. This index can be obtained by using the
:attr:`~graph_tool.Graph.vertex_index` attribute of the graph (which is
a *property map*, see :ref:`sec_property_maps`), or by converting the
vertex descriptor to an ``int``.
.. doctest::
>>> v = g.add_vertex()
>>> print(g.vertex_index[v])
11
>>> print(int(v))
11
Edges and vertices can also be removed at any time with the Edges and vertices can also be removed at any time with the
:meth:`~graph_tool.Graph.remove_vertex` and :meth:`~graph_tool.Graph.remove_edge` methods, :meth:`~graph_tool.Graph.remove_vertex` and :meth:`~graph_tool.Graph.remove_edge` methods,
...@@ -151,21 +167,48 @@ Edges and vertices can also be removed at any time with the ...@@ -151,21 +167,48 @@ Edges and vertices can also be removed at any time with the
>>> g.remove_edge(e) # e no longer exists >>> g.remove_edge(e) # e no longer exists
>>> g.remove_vertex(v2) # the second vertex is also gone >>> g.remove_vertex(v2) # the second vertex is also gone
.. note:: .. note::
Removing a vertex is an :math:`O(N)` operation. The vertices are Removing a vertex is typically an :math:`O(N)` operation. The
internally stored in a `STL vector <http://en.wikipedia.org/wiki/Vector_%28STL%29>`_, vertices are internally stored in a `STL vector
so removing an element somewhere in the middle of the list requires <http://en.wikipedia.org/wiki/Vector_%28STL%29>`_, so removing an
the shifting of the rest of the list. Thus, fast :math:`O(1)` element somewhere in the middle of the list requires the shifting of
removals are only possible either if one can guarantee that only the rest of the list. Thus, fast :math:`O(1)` removals are only
vertices in the end of the list are removed (the ones last added to possible either if one can guarantee that only vertices in the end of
the graph), or if the relative vertex ordering is invalidated. This the list are removed (the ones last added to the graph), or if the
last behavior can be achieved by passing the option ``fast == True``, relative vertex ordering is invalidated. This last behavior can be
to :meth:`~graph_tool.Graph.remove_vertex`, which causes vertex achieved by passing the option ``fast == True``, to
:meth:`~graph_tool.Graph.remove_vertex`, which causes the vertex
being deleted to be 'swapped' with the last vertex (i.e. with the being deleted to be 'swapped' with the last vertex (i.e. with the
largest index), which will in turn inherit the index of the vertex largest index), which will in turn inherit the index of the vertex
being deleted. being deleted.
.. warning::
Because of the above, removing a vertex with an index smaller than
:math:`N-1` will **invalidate either the last** (``fast = True``)
**or all** (``fast = False``) **descriptors pointing to vertices with
higher index**.
As a consequence, if more than one vertex is to be removed at a given
time, they should **always** be removed in decreasing index order:
.. code::
# 'del_list' is a list of vertex descriptors
for v in reversed(sorted(del_list)):
g.remove_vertex(v)
Alternatively (and preferably), a list (or any iterable) may be
passed directly as the ``vertex`` parameter of the
:meth:`~graph_tool.Graph.remove_vertex` function, and the above is
performed internally (in C++).
Note that property map values (see :ref:`sec_property_maps`) are
unaffected by the index changes due to vertex removal.
.. note::
Removing an edge is an :math:`O(k_{s} + k_{t})` operation, where Removing an edge is an :math:`O(k_{s} + k_{t})` operation, where
:math:`k_{s}` is the out-degree of the source vertex, and :math:`k_{s}` is the out-degree of the source vertex, and
...@@ -174,26 +217,7 @@ Edges and vertices can also be removed at any time with the ...@@ -174,26 +217,7 @@ Edges and vertices can also be removed at any time with the
`True`, in which case it becomes :math:`O(1)`, at the expense of `True`, in which case it becomes :math:`O(1)`, at the expense of
additional data of size :math:`O(E)`. additional data of size :math:`O(E)`.
Each vertex in a graph has an unique index, which is numbered from 0 to No edge descriptors are ever invalidated after edge removal.
N-1, where N is the number of vertices. This index can be obtained by
using the :attr:`~graph_tool.Graph.vertex_index` attribute of the graph
(which is a *property map*, see :ref:`sec_property_maps`), or by
converting the vertex descriptor to an ``int``.
.. doctest::
>>> v = g.add_vertex()
>>> print(g.vertex_index[v])
11
>>> print(int(v))
11
.. note::
Removing a vertex will cause the index of any vertex with a larger
index to be changed, so that the indexes *always* conform to the
:math:`[0, N-1]` range. However, property map values (see
:ref:`sec_property_maps`) are unaffected.
Since vertices are uniquely identifiable by their indexes, there is no Since vertices are uniquely identifiable by their indexes, there is no
need to keep the vertex descriptor lying around to access them at a need to keep the vertex descriptor lying around to access them at a
...@@ -238,6 +262,7 @@ its index will be "vacant", and the remaining indexes will be left ...@@ -238,6 +262,7 @@ its index will be "vacant", and the remaining indexes will be left
unmodified, and thus will not lie in the range :math:`[0, E-1]`. If a unmodified, and thus will not lie in the range :math:`[0, E-1]`. If a
new edge is added, it will reuse old indexes, in an increasing order. new edge is added, it will reuse old indexes, in an increasing order.
.. _sec_iteration: .. _sec_iteration:
Iterating over vertices and edges Iterating over vertices and edges
...@@ -306,7 +331,7 @@ vertices in the graph. ...@@ -306,7 +331,7 @@ vertices in the graph.
somewhere (such as in a list) and remove them only after no iterator somewhere (such as in a list) and remove them only after no iterator
is being used. Removal during iteration will cause bad things to is being used. Removal during iteration will cause bad things to
happen. happen.
.. _sec_property_maps: .. _sec_property_maps:
......
...@@ -101,8 +101,8 @@ public: ...@@ -101,8 +101,8 @@ public:
void PurgeEdges(); // removes filtered edges void PurgeEdges(); // removes filtered edges
void Clear(); void Clear();
void ClearEdges(); void ClearEdges();
void ShiftVertexProperty(boost::any map, size_t index) const; void ShiftVertexProperty(boost::any map, boost::python::object oindex) const;
void MoveVertexProperty(boost::any map, size_t index) const; void MoveVertexProperty(boost::any map, boost::python::object oindex) const;
void ReIndexVertexProperty(boost::any map, boost::any old_index) const; void ReIndexVertexProperty(boost::any map, boost::any old_index) const;
void CopyVertexProperty(const GraphInterface& src, boost::any prop_src, void CopyVertexProperty(const GraphInterface& src, boost::any prop_src,
boost::any prop_tgt); boost::any prop_tgt);
......
...@@ -45,15 +45,20 @@ const char* type_names[] = ...@@ -45,15 +45,20 @@ const char* type_names[] =
struct shift_vertex_property struct shift_vertex_property
{ {
template <class PropertyMap> template <class PropertyMap, class Vec>
void operator()(PropertyMap, const GraphInterface::multigraph_t& g, void operator()(PropertyMap, const GraphInterface::multigraph_t& g,
boost::any map, size_t vi, bool& found) const boost::any map, const Vec& vi, bool& found) const
{ {
try try
{ {
PropertyMap pmap = any_cast<PropertyMap>(map); PropertyMap pmap = any_cast<PropertyMap>(map);
for (size_t i = vi; i < num_vertices(g)-1; ++i) size_t back = num_vertices(g) - 1;
pmap[vertex(i,g)] = pmap[vertex(i+1,g)]; for (auto v : vi)
{
for (size_t i = v; i < back; ++i)
pmap[vertex(i, g)] = pmap[vertex(i + 1, g)];
back--;
}
found = true; found = true;
} }
catch (bad_any_cast&) {} catch (bad_any_cast&) {}
...@@ -61,8 +66,9 @@ struct shift_vertex_property ...@@ -61,8 +66,9 @@ struct shift_vertex_property
}; };
// this function will shift all the properties when a vertex is to be deleted // this function will shift all the properties when a vertex is to be deleted
void GraphInterface::ShiftVertexProperty(boost::any prop, size_t index) const void GraphInterface::ShiftVertexProperty(boost::any prop, python::object oindex) const
{ {
boost::multi_array_ref<int64_t,1> index = get_array<int64_t,1>(oindex);
bool found = false; bool found = false;
mpl::for_each<writable_vertex_properties> mpl::for_each<writable_vertex_properties>
(std::bind(shift_vertex_property(), std::placeholders::_1, std::ref(*_mg), (std::bind(shift_vertex_property(), std::placeholders::_1, std::ref(*_mg),
...@@ -73,14 +79,19 @@ void GraphInterface::ShiftVertexProperty(boost::any prop, size_t index) const ...@@ -73,14 +79,19 @@ void GraphInterface::ShiftVertexProperty(boost::any prop, size_t index) const
struct move_vertex_property struct move_vertex_property
{ {
template <class PropertyMap> template <class PropertyMap, class Vec>
void operator()(PropertyMap, const GraphInterface::multigraph_t& g, void operator()(PropertyMap, const GraphInterface::multigraph_t& g,
boost::any map, size_t vi, size_t back, bool& found) const boost::any map, const Vec& vi, size_t back,
bool& found) const
{ {
try try
{ {
PropertyMap pmap = any_cast<PropertyMap>(map); PropertyMap pmap = any_cast<PropertyMap>(map);
pmap[vertex(vi,g)] = pmap[vertex(back,g)]; for (auto v : vi)
{
pmap[vertex(v, g)] = pmap[vertex(back, g)];
back--;
}
found = true; found = true;
} }
catch (bad_any_cast&) {} catch (bad_any_cast&) {}
...@@ -88,8 +99,9 @@ struct move_vertex_property ...@@ -88,8 +99,9 @@ struct move_vertex_property
}; };
// this function will move the back of the property map when a vertex in the middle is to be deleted // this function will move the back of the property map when a vertex in the middle is to be deleted
void GraphInterface::MoveVertexProperty(boost::any prop, size_t index) const void GraphInterface::MoveVertexProperty(boost::any prop, python::object oindex) const
{ {
boost::multi_array_ref<int64_t,1> index = get_array<int64_t,1>(oindex);
size_t back = num_vertices(*_mg) - 1; size_t back = num_vertices(*_mg) - 1;
bool found = false; bool found = false;
mpl::for_each<writable_vertex_properties> mpl::for_each<writable_vertex_properties>
......
...@@ -142,33 +142,21 @@ python::object add_vertex(python::object g, size_t n) ...@@ -142,33 +142,21 @@ python::object add_vertex(python::object g, size_t n)
return python::object(PythonVertex(g, add_vertex(gi.GetGraph()))); return python::object(PythonVertex(g, add_vertex(gi.GetGraph())));
} }
struct shift_vertex_property
{
template <class Graph, class PropertyMap>
void operator()(const Graph& g, size_t vi, PropertyMap pmap)
const
{
size_t N = num_vertices(g);
if (N <= 1 || vi >= N - 1)
return;
for (size_t i = vi; i < N-1; ++i)
{
pmap[vertex(i, g)] = pmap[vertex(i+1, g)];
}
}
};
void remove_vertex(GraphInterface& gi, const python::object& v, bool fast) void remove_vertex(GraphInterface& gi, const python::object& oindex, bool fast)
{ {
PythonVertex& pv = python::extract<PythonVertex&>(v); boost::multi_array_ref<int64_t,1> index = get_array<int64_t,1>(oindex);
pv.CheckValid(); auto& g = gi.GetGraph();
GraphInterface::vertex_t dv = pv.GetDescriptor();
pv.SetValid(false);
if (fast) if (fast)
remove_vertex_fast(dv, gi.GetGraph()); {
for (auto v : index)
remove_vertex_fast(vertex(v, g), g);
}
else else
remove_vertex(dv, gi.GetGraph()); {
for (auto v : index)
remove_vertex(vertex(v, g), g);
}
} }
struct add_new_edge struct add_new_edge
......
...@@ -1039,7 +1039,7 @@ def edge_endpoint_property(g, prop, endpoint, eprop=None): ...@@ -1039,7 +1039,7 @@ def edge_endpoint_property(g, prop, endpoint, eprop=None):
@_limit_args({"htype": ["int8_t", "int32_t", "int64_t"]}) @_limit_args({"htype": ["int8_t", "int32_t", "int64_t"]})
def perfect_prop_hash(props, htype="int32_t"): def perfect_prop_hash(props, htype="int32_t"):
"""Given a list of property maps `props` of the same type, a derived list of """Given a list of property maps `props` of the same type, a derived list of
property maps with integral type `htype` is retured, where each value is property maps with integral type `htype` is returned, where each value is
replaced by a perfect (i.e. unique) hash value. replaced by a perfect (i.e. unique) hash value.
.. note:: .. note::
...@@ -1431,7 +1431,8 @@ class Graph(object): ...@@ -1431,7 +1431,8 @@ class Graph(object):
return (self.vertex(i) for i in range(pos, pos + n)) return (self.vertex(i) for i in range(pos, pos + n))
def remove_vertex(self, vertex, fast=False): def remove_vertex(self, vertex, fast=False):
r"""Remove a vertex from the graph. r"""Remove a vertex from the graph. If ``vertex`` is an iterable, it
should correspond to a sequence of vertices to be removed.
.. note:: .. note::
...@@ -1441,6 +1442,27 @@ class Graph(object): ...@@ -1441,6 +1442,27 @@ class Graph(object):
degree of the vertex being deleted, and :math:`k_{\text{last}}` is degree of the vertex being deleted, and :math:`k_{\text{last}}` is
the (total) degree of the vertex with the largest index. the (total) degree of the vertex with the largest index.
.. warning::
This operation may invalidate vertex descriptors. Vertices are always
indexed contiguously in the range :math:`[0, N-1]`, hence vertex
descriptors with an index higher than ``vertex`` will be invalidated
after removal (if ``fast == False``, otherwise only descriptors
pointing to vertices with the largest index will be invalidated).
Because of this, the only safe way to remove more than one vertex at
once is to sort them in decreasing index order:
.. code::
# 'del_list' is a list of vertex descriptors
for v in reversed(sorted(del_list)):
g.remove_vertex(v)
Alternatively (and preferably), a list (or iterable) may be passed
directly as the ``vertex`` parameter, and the above is performed
internally (in C++).
.. warning:: .. warning::
If ``fast == True``, the vertex being deleted is 'swapped' with the If ``fast == True``, the vertex being deleted is 'swapped' with the
...@@ -1451,20 +1473,35 @@ class Graph(object): ...@@ -1451,20 +1473,35 @@ class Graph(object):
""" """
self.__check_perms("del_vertex") self.__check_perms("del_vertex")
vertex = self.vertex(int(vertex))
index = self.vertex_index[vertex] try:
vs = numpy.array([int(vertex)], dtype="int64")
except TypeError:
try:
vs = numpy.asarray(vertex, dtype="int64")
except TypeError:
vs = numpy.asarray([int(v) for v in vertex], dtype="int64")
if len(vs) == 0:
return
vs = numpy.sort(vs)[::-1]
back = self.__graph.GetNumberOfVertices(False) - 1 back = self.__graph.GetNumberOfVertices(False) - 1
if vs.max() > back:
raise ValueError("Vertex index %d is invalid" % vs.max())
# move / shift all known property maps # move / shift all known property maps
if index != back: if len(vs) > 0 or vs[0] != back:
for pmap in self.__known_properties.values(): for pmap in self.__known_properties.values():
if pmap() is not None and pmap().key_type() == "v" and pmap().is_writable(): if pmap() is not None and pmap().key_type() == "v" and pmap().is_writable():
if fast: if fast:
self.__graph.MoveVertexProperty(pmap()._PropertyMap__map.get_map(), index) self.__graph.MoveVertexProperty(pmap()._PropertyMap__map.get_map(), vs)
else: else:
self.__graph.ShiftVertexProperty(pmap()._PropertyMap__map.get_map(), index) self.__graph.ShiftVertexProperty(pmap()._PropertyMap__map.get_map(), vs)
libcore.remove_vertex(self.__graph, vertex, fast) libcore.remove_vertex(self.__graph, vs, fast)
def clear_vertex(self, vertex): def clear_vertex(self, vertex):
"""Remove all in and out-edges from the given vertex.""" """Remove all in and out-edges from the given vertex."""
......
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