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

Implement options "hashed" and "string_vals" in Graph.add_edge_list()

parent bc1feb23
......@@ -20,20 +20,13 @@
#include "graph_python_interface.hh"
#include <boost/python.hpp>
#include <boost/python/stl_iterator.hpp>
#include <set>
using namespace std;
using namespace boost;
using namespace graph_tool;
namespace boost
{
size_t hash_value(const boost::python::object& o)
{
return std::hash<boost::python::object>()(o);
}
}
namespace graph_tool
{
......@@ -390,7 +383,6 @@ do_get_edge_index(GraphInterface& g)
(g.GetEdgeIndex());
}
template <class ValueList>
struct add_edge_list
{
......@@ -416,10 +408,10 @@ struct add_edge_list
if (edge_list.shape()[1] < 2)
throw GraphException("Second dimension in edge list must be of size (at least) two");
for (size_t i = 0; i < edge_list.shape()[0]; ++i)
for (const auto& e : edge_list)
{
size_t s = edge_list[i][0];
size_t t = edge_list[i][1];
size_t s = e[0];
size_t t = e[1];
while (s >= num_vertices(g) || t >= num_vertices(g))
add_vertex(g);
add_edge(vertex(s, g), vertex(t, g), g);
......@@ -433,8 +425,9 @@ struct add_edge_list
void do_add_edge_list(GraphInterface& gi, python::object aedge_list)
{
typedef mpl::vector<bool, uint8_t, uint32_t, int16_t, int32_t, int64_t, uint64_t,
unsigned long int, double, long double> vals_t;
typedef mpl::vector<bool, char, uint8_t, uint16_t, uint32_t, uint64_t,
int8_t, int16_t, int32_t, int64_t, uint64_t, double,
long double> vals_t;
bool found = false;
run_action<>()(gi, std::bind(add_edge_list<vals_t>(), placeholders::_1, aedge_list,
std::ref(found)))();
......@@ -442,6 +435,156 @@ void do_add_edge_list(GraphInterface& gi, python::object aedge_list)
throw GraphException("Invalid type for edge list; must be two-dimensional with a scalar type");
}
template <class ValueList>
struct add_edge_list_hash
{
template <class Graph, class VProp>
void operator()(Graph& g, python::object aedge_list, VProp vmap,
bool& found, bool use_str) const
{
boost::mpl::for_each<ValueList>(std::bind(dispatch(), std::ref(g),
std::ref(aedge_list), std::ref(vmap),
std::ref(found), placeholders::_1));
if (!found)
{
if (use_str)
dispatch()(g, aedge_list, vmap, found, std::string());
else
dispatch()(g, aedge_list, vmap, found, python::object());
}
}
struct dispatch
{
template <class Graph, class VProp, class Value>
void operator()(Graph& g, python::object& aedge_list, VProp& vmap,
bool& found, Value) const
{
if (found)
return;
try
{
boost::multi_array_ref<Value, 2> edge_list = get_array<Value, 2>(aedge_list);
unordered_map<Value, size_t> vertices;
if (edge_list.shape()[1] < 2)
throw GraphException("Second dimension in edge list must be of size (at least) two");
auto get_vertex = [&] (const Value& r) -> size_t
{
auto iter = vertices.find(r);
if (iter == vertices.end())
{
auto v = add_vertex(g);
vertices[r] = v;
vmap[v] = lexical_cast<typename property_traits<VProp>::value_type>(r);
return v;
}
return iter->second;
};
for (const auto& e : edge_list)
{
size_t s = get_vertex(e[0]);
size_t t = get_vertex(e[1]);
add_edge(vertex(s, g), vertex(t, g), g);
}
found = true;
}
catch (invalid_numpy_conversion& e) {}
}
template <class Graph, class VProp>
void operator()(Graph& g, python::object& edge_list, VProp& vmap,
bool& found, std::string) const
{
if (found)
return;
try
{
unordered_map<std::string, size_t> vertices;
auto get_vertex = [&] (const std::string& r) -> size_t
{
auto iter = vertices.find(r);
if (iter == vertices.end())
{
auto v = add_vertex(g);
vertices[r] = v;
vmap[v] = lexical_cast<typename property_traits<VProp>::value_type>(r);
return v;
}
return iter->second;
};
python::stl_input_iterator<python::object> iter(edge_list), end;
for (; iter != end; ++iter)
{
const auto& e = *iter;
size_t s = get_vertex(python::extract<std::string>(e[0]));
size_t t = get_vertex(python::extract<std::string>(e[1]));
add_edge(vertex(s, g), vertex(t, g), g);
}
found = true;
}
catch (invalid_numpy_conversion& e) {}
}
template <class Graph, class VProp>
void operator()(Graph& g, python::object& edge_list, VProp& vmap,
bool& found, python::object) const
{
if (found)
return;
try
{
unordered_map<python::object, size_t> vertices;
auto get_vertex = [&] (const python::object& r) -> size_t
{
auto iter = vertices.find(r);
if (iter == vertices.end())
{
auto v = add_vertex(g);
vertices[r] = v;
vmap[v] = python::extract<typename property_traits<VProp>::value_type>(r);
return v;
}
return iter->second;
};
python::stl_input_iterator<python::object> iter(edge_list), end;
for (; iter != end; ++iter)
{
const auto& e = *iter;
size_t s = get_vertex(e[0]);
size_t t = get_vertex(e[1]);
add_edge(vertex(s, g), vertex(t, g), g);
}
found = true;
}
catch (invalid_numpy_conversion& e) {}
}
};
};
void do_add_edge_list_hashed(GraphInterface& gi, python::object aedge_list,
boost::any& vertex_map, bool is_str)
{
typedef mpl::vector<bool, char, uint8_t, uint16_t, uint32_t, uint64_t,
int8_t, int16_t, int32_t, int64_t, uint64_t, double,
long double> vals_t;
bool found = false;
run_action<graph_tool::detail::all_graph_views, boost::mpl::true_>()
(gi, std::bind(add_edge_list_hash<vals_t>(), placeholders::_1,
aedge_list, placeholders::_2, std::ref(found),
is_str),
writable_vertex_properties())(vertex_map);
if (!found)
throw GraphException("Invalid type for edge list; must be two-dimensional with a scalar or string type");
}
} // namespace graph_tool
......@@ -496,6 +639,7 @@ void export_python_interface()
def("remove_vertex", graph_tool::remove_vertex);
def("remove_edge", graph_tool::remove_edge);
def("add_edge_list", graph_tool::do_add_edge_list);
def("add_edge_list_hashed", graph_tool::do_add_edge_list_hashed);
def("get_edge", get_edge);
def("get_vertex_index", get_vertex_index);
......
......@@ -30,16 +30,11 @@ namespace std
{
size_t operator()(const boost::python::object& o) const
{
return boost::python::extract<size_t>(o.attr("__hash__")());
return std::hash<int64_t>()(boost::python::extract<int64_t>(o.attr("__hash__")()));
}
};
}
namespace boost
{
size_t hash_value(const boost::python::object& o);
}
#include <boost/graph/graph_traits.hpp>
#include <boost/mpl/logical.hpp>
#include <boost/iterator/iterator_facade.hpp>
......
......@@ -44,6 +44,7 @@ using namespace std;
typedef boost::mpl::map<
boost::mpl::pair<bool, boost::mpl::int_<NPY_BOOL> >,
boost::mpl::pair<char, boost::mpl::int_<NPY_CHAR> >,
boost::mpl::pair<int8_t, boost::mpl::int_<NPY_INT8> >,
boost::mpl::pair<uint8_t, boost::mpl::int_<NPY_UINT8> >,
boost::mpl::pair<int16_t, boost::mpl::int_<NPY_INT16> >,
......
......@@ -216,19 +216,18 @@ def _python_type(type_name):
return str
return object
def _gt_type(obj):
t = type(obj)
if t is numpy.longlong or t is numpy.uint64:
return "long long"
if issubclass(t, numpy.int16):
return "short"
if t is int or issubclass(t, numpy.int):
return "int"
if t in (numpy.int16, numpy.uint16, numpy.int8, numpy.uint8):
return "int16_t"
if t in (int, numpy.int32, numpy.uint32):
return "int32_t"
if t in (numpy.longlong, numpy.uint64, numpy.int64):
return "int64_t"
if t in (float, numpy.float, numpy.float16, numpy.float32, numpy.float64):
return "double"
if t is numpy.float128:
return "long double"
if t is float or issubclass(t, numpy.float):
return "double"
if t is str:
return "string"
if t is bool:
......@@ -1872,16 +1871,38 @@ class Graph(object):
self.__check_perms("del_edge")
return libcore.remove_edge(self.__graph, edge)
def add_edge_list(self, edge_list):
def add_edge_list(self, edge_list, hashed=False, string_vals=False):
"""Add a list of edges to the graph, given by ``edge_list``, which can
be a list of ``(source, target)`` pairs where both ``source`` and
``target`` are vertex indexes, or a :class:`~numpy.ndarray` of shape
``(E,2)``, where ``E`` is the number of edges, and each line specifies a
``(source, target)`` pair. If the list references vertices which do not
exist in the graph, they will be created."""
exist in the graph, they will be created.
Optionally, if ``hashed == True``, the vertex values in the edge list
are not assumed to correspond to vertex indices directly. In this case
they will be mapped to vertex indices in according to the order in which
they are encountered. In this case, a vertex property map with the
vertex values is returned. If ``string_vals == True``, the algorithm
assumes that the vertex values are strings. Otherwise, they will be
assumed to be numeric if ``edge_list`` is a :class:`~numpy.ndarray`, or
arbitrary python objects if it is not.
"""
self.__check_perms("add_edge")
edges = numpy.asarray(edge_list)
libcore.add_edge_list(self.__graph, edges)
if not hashed:
edges = numpy.asarray(edge_list)
libcore.add_edge_list(self.__graph, edges)
else:
if isinstance(edge_list, numpy.ndarray):
vprop = self.new_vertex_property(_gt_type(edge_list.dtype))
elif string_vals:
vprop = self.new_vertex_property("string")
else:
vprop = self.new_vertex_property("object")
libcore.add_edge_list_hashed(self.__graph, edge_list,
_prop("v", self, vprop),
string_vals)
return vprop
def set_fast_edge_removal(self, fast=True):
r"""If ``fast == True`` the fast :math:`O(1)` removal of edges will be
......
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