Commit d6c4ad94 authored by Tiago Peixoto's avatar Tiago Peixoto

Implement array-based vertices/edges access interface

parent 80692ffd
......@@ -14,9 +14,22 @@
See :ref:`sec_iteration` for more documentation and examples.
Iterator-based interface:
.. automethod:: vertices
.. automethod:: edges
Array-based interface:
.. automethod:: get_vertices
.. automethod:: get_edges
.. automethod:: get_out_edges
.. automethod:: get_in_edges
.. automethod:: get_out_neighbours
.. automethod:: get_in_neighbours
.. automethod:: get_out_degrees
.. automethod:: get_in_degrees
.. container:: sec_title
Obtaining vertex and edge descriptors
......@@ -71,7 +84,7 @@
.. note::
These functions do not actually modify the graph, and are fully
reversible. They are also very cheap, and have an :math:`O(1)`
reversible. They are also very cheap, with an :math:`O(1)`
complexity.
.. automethod:: set_directed
......
......@@ -308,7 +308,7 @@ and :meth:`~graph_tool.Vertex.in_neighbours` methods, respectively.
print(w)
# the edge and neighbours order always match
for e,w in izip(v.out_edges(), v.out_neighbours()):
for e, w in izip(v.out_edges(), v.out_neighbours()):
assert(e.target() == w)
The code above will print the out-edges and out-neighbours of all
......@@ -322,8 +322,48 @@ vertices in the graph.
somewhere (such as in a list) and remove them only after no iterator
is being used. Removal during iteration will cause bad things to
happen.
Fast iteration over vertices and edges
""""""""""""""""""""""""""""""""""""""
While convenient, looping over the graph as described in the previous
section is not the most efficient approach. This is because the loops
are performed in pure Python, and hence it undermines the main feature
of the library, which is the offloading of loops from Python to
C++. Following the :mod:`numpy` philosophy, :mod:`graph_tool` also
provides an array-based interface that avoids loops in Python. This is
done with the :meth:`~graph_tool.Graph.get_vertices`,
:meth:`~graph_tool.Graph.get_edges`,
:meth:`~graph_tool.Graph.get_out_edges`,
:meth:`~graph_tool.Graph.get_in_edges`,
:meth:`~graph_tool.Graph.get_out_neighbours`,
:meth:`~graph_tool.Graph.get_in_neighbours`,
:meth:`~graph_tool.Graph.get_out_degrees` and
:meth:`~graph_tool.Graph.get_in_degrees` methods, which return
:class:`numpy.ndarray` instances instead of iterators.
For example, using this interface we can get the out-degree of each node via:
.. testcode::
print(g.get_out_degrees(g.get_vertices()))
.. testoutput::
[1 0 1 0 0 0 0 0 0 0 0 0]
or the sum of the product of the in and out-degrees of the endpoints of
each edge with:
.. testcode::
edges = g.get_edges()
print((edges[:,0] * edges[:,1]).sum())
.. testoutput::
6
.. _sec_property_maps:
Property maps
......
......@@ -345,6 +345,160 @@ python::object GraphInterface::degree_map(string deg, boost::any weight) const
return deg_map;
}
python::object get_vertex_list(GraphInterface& gi)
{
std::vector<size_t> vlist;
vlist.reserve(gi.get_num_vertices());
run_action<>()(gi,
[&](auto& g)
{
for (auto v: vertices_range(g))
vlist.push_back(v);
})();
return wrap_vector_owned(vlist);
}
python::object get_edge_list(GraphInterface& gi)
{
std::vector<size_t> elist;
run_action<>()(gi,
[&](auto& g)
{
auto edge_index = get(edge_index_t(), g);
for (auto e: edges_range(g))
{
elist.push_back(source(e, g));
elist.push_back(target(e, g));
elist.push_back(edge_index[e]);
}
})();
return wrap_vector_owned(elist);
}
python::object get_out_edge_list(GraphInterface& gi, size_t v)
{
std::vector<size_t> elist;
run_action<>()(gi,
[&](auto& g)
{
if (!is_valid_vertex(v, g))
throw ValueException("invalid vertex: " +
lexical_cast<string>(v));
auto edge_index = get(edge_index_t(), g);
elist.reserve(3 * out_degree(v, g));
for (auto e: out_edges_range(v, g))
{
elist.push_back(source(e, g));
elist.push_back(target(e, g));
elist.push_back(edge_index[e]);
}
})();
return wrap_vector_owned(elist);
}
python::object get_in_edge_list(GraphInterface& gi, size_t v)
{
std::vector<size_t> elist;
run_action<>()(gi,
[&](auto& g)
{
if (!is_valid_vertex(v, g))
throw ValueException("invalid vertex: " +
lexical_cast<string>(v));
auto edge_index = get(edge_index_t(), g);
elist.reserve(3 * in_degree(v, g));
for (auto e: in_edges_range(v, g))
{
elist.push_back(source(e, g));
elist.push_back(target(e, g));
elist.push_back(edge_index[e]);
}
})();
return wrap_vector_owned(elist);
}
python::object get_out_neighbours_list(GraphInterface& gi, size_t v)
{
std::vector<size_t> vlist;
run_action<>()(gi,
[&](auto& g)
{
if (!is_valid_vertex(v, g))
throw ValueException("invalid vertex: " +
lexical_cast<string>(v));
vlist.reserve(out_degree(v, g));
for (auto u: out_neighbours_range(v, g))
vlist.push_back(u);
})();
return wrap_vector_owned(vlist);
}
python::object get_in_neighbours_list(GraphInterface& gi, size_t v)
{
std::vector<size_t> vlist;
run_action<>()(gi,
[&](auto& g)
{
if (!is_valid_vertex(v, g))
throw ValueException("invalid vertex: " +
lexical_cast<string>(v));
vlist.reserve(in_degree(v, g));
for (auto u: in_neighbours_range(v, g))
vlist.push_back(u);
})();
return wrap_vector_owned(vlist);
}
python::object get_degree_list(GraphInterface& gi, python::object ovlist,
boost::any eprop, bool out)
{
python::object ret;
auto vlist = get_array<uint64_t,1>(ovlist);
typedef UnityPropertyMap<size_t,
graph_traits<GraphInterface::multigraph_t>::edge_descriptor>
empty_t;
if (eprop.empty())
{
eprop = empty_t();
}
else
{
if (!belongs<edge_scalar_properties>()(eprop))
throw ValueException("edge weight property map must be of scalar type");
}
typedef mpl::push_back<edge_scalar_properties,
empty_t>::type eprops_t;
auto get_degs = [&](auto deg)
{
run_action<>()(gi,
[&](auto& g, auto& ew)
{
typedef typename std::remove_reference
<decltype(ew)>::type::value_type val_t;
std::vector<val_t> dlist;
dlist.reserve(vlist.size());
for (auto v : vlist)
{
if (!is_valid_vertex(v, g))
throw ValueException("invalid vertex: " +
lexical_cast<string>(v));
dlist.push_back(val_t(deg(v, g, ew)));
}
ret = wrap_vector_owned(dlist);
}, eprops_t())(eprop);
};
if (out)
get_degs(out_degreeS());
else
get_degs(in_degreeS());
return ret;
}
//
// Below are the functions with will properly register all the types to python,
// for every filter, type, etc.
......@@ -570,6 +724,14 @@ void export_python_interface()
def("add_edge_list_iter", graph_tool::do_add_edge_list_iter);
def("get_edge", get_edge);
def("get_vertex_list", get_vertex_list);
def("get_edge_list", get_edge_list);
def("get_out_edge_list", get_out_edge_list);
def("get_in_edge_list", get_in_edge_list);
def("get_out_neighbours_list", get_out_neighbours_list);
def("get_in_neighbours_list", get_in_neighbours_list);
def("get_degree_list", get_degree_list);
def("get_vertex_index", get_vertex_index);
def("get_edge_index", do_get_edge_index);
......
......@@ -1719,6 +1719,25 @@ class Graph(object):
"""
return libcore.get_vertices(self.__graph)
def get_vertices(self):
"""Return a :class:`numpy.ndarray` with the vertex indices.
.. note::
The order of the vertices is identical to
:meth:`~graph_tool.Graph.vertices`.
Examples
--------
>>> g = gt.Graph()
>>> g.add_vertex(5)
<...>
>>> g.get_vertices()
array([0, 1, 2, 3, 4], dtype=uint64)
"""
return libcore.get_vertex_list(self.__graph)
def vertex(self, i, use_index=True, add_missing=False):
"""Return the vertex with index ``i``. If ``use_index=False``, the
``i``-th vertex is returned (which can differ from the vertex with index
......@@ -1777,6 +1796,124 @@ class Graph(object):
"""
return libcore.get_edges(self.__graph)
def get_edges(self):
"""Return a :class:`numpy.ndarray` containing the edges. The shape of
the array will be ``(E, 3)``, where ``E`` is the number of edges, and
each line will contain the source, target and index of an edge.
.. note::
The order of the edges is identical to
:meth:`~graph_tool.Graph.edges`.
Examples
--------
>>> g = gt.random_graph(6, lambda: 1, directed=False)
>>> g.get_edges()
array([[0, 4, 0],
[2, 1, 2],
[5, 3, 1]], dtype=uint64)
"""
edges = libcore.get_edge_list(self.__graph)
E = edges.shape[0] // 3
return numpy.reshape(edges, (E, 3))
def get_out_edges(self, v):
"""Return a :class:`numpy.ndarray` containing the out-edges of vertex ``v``. The
shape of the array will be ``(k, 3)``, where ``k`` is the out-degree of
``v``, and each line will contain the source, target and index of an
edge.
Examples
--------
>>> g = gt.collection.data["pgp-strong-2009"]
>>> g.get_out_edges(66)
array([[ 66, 63, 5266],
[ 66, 20369, 5267],
[ 66, 13980, 5268],
[ 66, 8687, 5269],
[ 66, 38674, 5270]], dtype=uint64)
"""
edges = libcore.get_out_edge_list(self.__graph, int(v))
E = edges.shape[0] // 3
return numpy.reshape(edges, (E, 3))
def get_in_edges(self, v):
"""Return a :class:`numpy.ndarray` containing the out-edges of vertex ``v``. The
shape of the array will be ``(k, 3)``, where ``k`` is the out-degree of
``v``, and each line will contain the source, target and index of an
edge.
Examples
--------
>>> g = gt.collection.data["pgp-strong-2009"]
>>> g.get_in_edges(66)
array([[ 8687, 66, 179681],
[ 20369, 66, 255033],
[ 38674, 66, 300230]], dtype=uint64)
"""
edges = libcore.get_in_edge_list(self.__graph, int(v))
E = edges.shape[0] // 3
return numpy.reshape(edges, (E, 3))
def get_out_neighbours(self, v):
"""Return a :class:`numpy.ndarray` containing the out-neighbours of vertex
``v``.
Examples
--------
>>> g = gt.collection.data["pgp-strong-2009"]
>>> g.get_out_neighbours(66)
array([ 63, 20369, 13980, 8687, 38674], dtype=uint64)
"""
return libcore.get_out_neighbours_list(self.__graph, int(v))
def get_in_neighbours(self, v):
"""Return a :class:`numpy.ndarray` containing the in-neighbours of vertex ``v``.
Examples
--------
>>> g = gt.collection.data["pgp-strong-2009"]
>>> g.get_in_neighbours(66)
array([ 8687, 20369, 38674], dtype=uint64)
"""
return libcore.get_in_neighbours_list(self.__graph, int(v))
def get_out_degrees(self, vs, eweight=None):
"""Return a :class:`numpy.ndarray` containing the out-degrees of vertex list
``vs``. If supplied, the degrees will be weighted according to the edge
:class:`~graph_tool.PropertyMap` ``eweight``.
Examples
--------
>>> g = gt.collection.data["pgp-strong-2009"]
>>> g.get_out_degrees([42, 666])
array([20, 38], dtype=uint64)
"""
return libcore.get_degree_list(self.__graph,
numpy.asarray(vs, dtype="uint64"),
_prop("e", self, eweight), True)
def get_in_degrees(self, vs, eweight=None):
"""Return a :class:`numpy.ndarray` containing the in-degrees of vertex list
``vs``. If supplied, the degrees will be weighted according to the edge
:class:`~graph_tool.PropertyMap` ``eweight``.
Examples
--------
>>> g = gt.collection.data["pgp-strong-2009"]
>>> g.get_in_degrees([42, 666])
array([20, 39], dtype=uint64)
"""
return libcore.get_degree_list(self.__graph,
numpy.asarray(vs, dtype="uint64"),
_prop("e", self, eweight), False)
def add_vertex(self, n=1):
"""Add a vertex to the graph, and return it. If ``n != 1``, ``n``
vertices are inserted and an iterator over the new vertices is returned.
......
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