Commit 3db1d2d5 authored by Tiago Peixoto's avatar Tiago Peixoto
Browse files

Graph.add_edge_list(): improve handling of hashed=True for arbitrary types

parent 541f949c
Pipeline #672 passed with stage
in 666 minutes and 7 seconds
...@@ -854,7 +854,7 @@ void do_add_edge_list(GraphInterface& gi, python::object aedge_list, ...@@ -854,7 +854,7 @@ void do_add_edge_list(GraphInterface& gi, python::object aedge_list,
python::object eprops); python::object eprops);
void do_add_edge_list_hashed(GraphInterface& gi, python::object aedge_list, void do_add_edge_list_hashed(GraphInterface& gi, python::object aedge_list,
boost::any& vertex_map, bool is_str, boost::any& vertex_map,
python::object eprops); python::object eprops);
void do_add_edge_list_iter(GraphInterface& gi, python::object edge_list, void do_add_edge_list_iter(GraphInterface& gi, python::object edge_list,
......
...@@ -117,285 +117,195 @@ void do_add_edge_list(GraphInterface& gi, python::object aedge_list, ...@@ -117,285 +117,195 @@ 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"); throw GraphException("Invalid type for edge list; must be two-dimensional with a scalar type");
} }
template <class ValueList>
struct add_edge_list_hash struct add_edge_list_iter
{ {
template <class Graph, class VProp> template <class Graph>
void operator()(Graph& g, python::object aedge_list, VProp vmap, void operator()(Graph& g, python::object& edge_list,
bool& found, bool use_str, python::object& eprops) const python::object& oeprops) const
{ {
boost::mpl::for_each<ValueList>(std::bind(dispatch(), std::ref(g), typedef typename graph_traits<Graph>::edge_descriptor edge_t;
std::ref(aedge_list), std::ref(vmap), vector<DynamicPropertyMapWrap<python::object, edge_t>> eprops;
std::ref(found), std::ref(eprops), python::stl_input_iterator<boost::any> piter(oeprops), pend;
std::placeholders::_1)); for (; piter != pend; ++piter)
if (!found) eprops.emplace_back(*piter, writable_edge_properties());
{
if (use_str)
dispatch()(g, aedge_list, vmap, found, eprops, std::string());
else
dispatch()(g, aedge_list, vmap, found, eprops, python::object());
}
}
struct dispatch python::stl_input_iterator<python::object> iter(edge_list), end;
{ for (; iter != end; ++iter)
template <class Graph, class VProp, class Value>
void operator()(Graph& g, python::object& aedge_list, VProp& vmap,
bool& found, python::object& oeprops, Value) const
{ {
if (found) const auto& row = *iter;
return; python::stl_input_iterator<python::object> eiter(row), eend;
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");
typedef typename graph_traits<Graph>::edge_descriptor edge_t;
vector<DynamicPropertyMapWrap<Value, edge_t>> eprops;
python::stl_input_iterator<boost::any> iter(oeprops), end;
for (; iter != end; ++iter)
eprops.emplace_back(*iter, writable_edge_properties());
size_t n_props = std::min(eprops.size(), edge_list.shape()[1] - 2);
auto get_vertex = [&] (const Value& r) -> size_t size_t s = 0;
{ size_t t = 0;
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) typename graph_traits<Graph>::edge_descriptor e;
size_t i = 0;
for(; eiter != eend; ++eiter)
{
if (i >= eprops.size() + 2)
break;
const auto& val = *eiter;
switch (i)
{ {
size_t s = get_vertex(e[0]); case 0:
size_t t = get_vertex(e[1]); s = python::extract<size_t>(val);
auto ne = add_edge(vertex(s, g), vertex(t, g), g).first; while (s >= num_vertices(g))
for (size_t i = 0; i < n_props; ++i) add_vertex(g);
break;
case 1:
t = python::extract<size_t>(val);
while (t >= num_vertices(g))
add_vertex(g);
e = add_edge(vertex(s, g), vertex(t, g), g).first;
break;
default:
try
{ {
try put(eprops[i - 2], e, val);
{ }
put(eprops[i], ne, e[i + 2]); catch(bad_lexical_cast&)
} {
catch(bad_lexical_cast&) throw ValueException("Invalid edge property value: " +
{ python::extract<string>(python::str(val))());
throw ValueException("Invalid edge property value: " +
lexical_cast<string>(e[i + 2]));
}
} }
} }
found = true; i++;
} }
catch (InvalidNumpyConversion& e) {}
} }
}
};
template <class Graph, class VProp> void do_add_edge_list_iter(GraphInterface& gi, python::object edge_list,
void operator()(Graph& g, python::object& edge_list, VProp& vmap, python::object eprops)
bool& found, python::object& oeprops, std::string) const {
run_action<>()
(gi,
[&](auto&& graph)
{
return add_edge_list_iter()
(std::forward<decltype(graph)>(graph), edge_list, eprops);
})();
}
struct add_edge_list_hash
{
template <class Graph, class VProp>
void operator()(Graph& g, python::object aedge_list, VProp vmap,
python::object& eprops) const
{
typedef typename property_traits<VProp>::value_type val_t;
if constexpr (is_scalar_v<val_t>)
{ {
if (found)
return;
try try
{ {
unordered_map<std::string, size_t> vertices; numpy_dispatch(g, aedge_list, vmap, eprops);
}
catch (InvalidNumpyConversion&)
{
dispatch(g, aedge_list, vmap, eprops);
}
}
else
{
dispatch(g, aedge_list, vmap, eprops);
}
}
typedef typename graph_traits<Graph>::edge_descriptor edge_t; template <class Graph, class VProp>
vector<DynamicPropertyMapWrap<python::object, edge_t>> eprops; void numpy_dispatch(Graph& g, python::object& aedge_list, VProp& vmap,
python::stl_input_iterator<boost::any> piter(oeprops), pend; python::object& oeprops) const
for (; piter != pend; ++piter) {
eprops.emplace_back(*piter, writable_edge_properties()); typedef typename property_traits<VProp>::value_type val_t;
auto get_vertex = [&] (const std::string& r) -> size_t boost::multi_array_ref<val_t, 2> edge_list = get_array<val_t, 2>(aedge_list);
{ typedef typename std::conditional_t<std::is_integral_v<val_t>,
auto iter = vertices.find(r); gt_hash_map<val_t, size_t>,
if (iter == vertices.end()) unordered_map<val_t, size_t>> vmap_t;
{ vmap_t vertices;
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; if (edge_list.shape()[1] < 2)
for (; iter != end; ++iter) throw GraphException("Second dimension in edge list must be of size (at least) two");
{
const auto& row = *iter;
python::stl_input_iterator<python::object> eiter(row), eend; typedef typename graph_traits<Graph>::edge_descriptor edge_t;
vector<DynamicPropertyMapWrap<val_t, edge_t>> eprops;
python::stl_input_iterator<boost::any> iter(oeprops), end;
for (; iter != end; ++iter)
eprops.emplace_back(*iter, writable_edge_properties());
size_t s = 0; size_t n_props = std::min(eprops.size(), edge_list.shape()[1] - 2);
size_t t = 0;
typename graph_traits<Graph>::edge_descriptor e; auto get_vertex = [&] (const val_t& r) -> size_t
size_t i = 0; {
for(; eiter != eend; ++eiter) auto iter = vertices.find(r);
{ if (iter == vertices.end())
if (i >= eprops.size() + 2) {
break; auto v = add_vertex(g);
const auto& val = *eiter; vertices[r] = v;
switch (i) vmap[v] = r;
{ return v;
case 0:
s = get_vertex(python::extract<std::string>(val));
while (s >= num_vertices(g))
add_vertex(g);
break;
case 1:
t = get_vertex(python::extract<std::string>(val));
while (t >= num_vertices(g))
add_vertex(g);
e = add_edge(vertex(s, g), vertex(t, g), g).first;
break;
default:
try
{
put(eprops[i - 2], e, val);
}
catch(bad_lexical_cast&)
{
throw ValueException("Invalid edge property value: " +
python::extract<string>(python::str(val))());
}
}
i++;
}
} }
found = true; return iter->second;
} };
catch (InvalidNumpyConversion& e) {}
}
template <class Graph, class VProp> for (const auto& e : edge_list)
void operator()(Graph& g, python::object& edge_list, VProp& vmap,
bool& found, python::object& oeprops, python::object) const
{ {
if (found) size_t s = get_vertex(e[0]);
return; size_t t = get_vertex(e[1]);
try auto ne = add_edge(vertex(s, g), vertex(t, g), g).first;
for (size_t i = 0; i < n_props; ++i)
{ {
unordered_map<python::object, size_t> vertices; try
typedef typename graph_traits<Graph>::edge_descriptor edge_t;
vector<DynamicPropertyMapWrap<python::object, edge_t>> eprops;
python::stl_input_iterator<boost::any> piter(oeprops), pend;
for (; piter != pend; ++piter)
eprops.emplace_back(*piter, writable_edge_properties());
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& row = *iter; put(eprops[i], ne, e[i + 2]);
}
python::stl_input_iterator<python::object> eiter(row), eend; catch(bad_lexical_cast&)
{
size_t s = 0; throw ValueException("Invalid edge property value: " +
size_t t = 0; lexical_cast<string>(e[i + 2]));
typename graph_traits<Graph>::edge_descriptor e;
size_t i = 0;
for(; eiter != eend; ++eiter)
{
if (i >= eprops.size() + 2)
break;
const auto& val = *eiter;
switch (i)
{
case 0:
s = get_vertex(val);
while (s >= num_vertices(g))
add_vertex(g);
break;
case 1:
t = get_vertex(val);
while (t >= num_vertices(g))
add_vertex(g);
e = add_edge(vertex(s, g), vertex(t, g), g).first;
break;
default:
try
{
put(eprops[i - 2], e, val);
}
catch(bad_lexical_cast&)
{
throw ValueException("Invalid edge property value: " +
python::extract<string>(python::str(val))());
}
}
i++;
}
} }
found = true;
} }
catch (InvalidNumpyConversion& e) {}
} }
}; }
};
void do_add_edge_list_hashed(GraphInterface& gi, python::object aedge_list, template <class Graph, class VProp>
boost::any& vertex_map, bool is_str, void dispatch(Graph& g, python::object& edge_list, VProp& vmap,
python::object eprops) python::object& oeprops) const
{ {
typedef mpl::vector<bool, char, uint8_t, uint16_t, uint32_t, uint64_t, typedef typename property_traits<VProp>::value_type val_t;
int8_t, int16_t, int32_t, int64_t, uint64_t, double,
long double> vals_t;
bool found = false;
run_action<graph_tool::all_graph_views, boost::mpl::true_>()
(gi,
[&](auto&& graph, auto&& a2)
{
return add_edge_list_hash<vals_t>()(
std::forward<decltype(graph)>(graph), aedge_list,
std::forward<decltype(a2)>(a2), found, is_str, eprops);
},
writable_vertex_properties())(vertex_map);
}
typedef typename std::conditional_t<std::is_integral_v<val_t>,
gt_hash_map<val_t, size_t>,
unordered_map<val_t, size_t>> vmap_t;
vmap_t vertices;
struct add_edge_list_iter
{
template <class Graph>
void operator()(Graph& g, python::object& edge_list,
python::object& oeprops) const
{
typedef typename graph_traits<Graph>::edge_descriptor edge_t; typedef typename graph_traits<Graph>::edge_descriptor edge_t;
vector<DynamicPropertyMapWrap<python::object, edge_t>> eprops; vector<DynamicPropertyMapWrap<python::object, edge_t>> eprops;
python::stl_input_iterator<boost::any> piter(oeprops), pend; python::stl_input_iterator<boost::any> piter(oeprops), pend;
for (; piter != pend; ++piter) for (; piter != pend; ++piter)
eprops.emplace_back(*piter, writable_edge_properties()); eprops.emplace_back(*piter, writable_edge_properties());
auto get_vertex = [&] (const val_t& r) -> size_t
{
auto iter = vertices.find(r);
if (iter == vertices.end())
{
auto v = add_vertex(g);
vertices[r] = v;
vmap[v] = r;
return v;
}
return iter->second;
};
python::stl_input_iterator<python::object> iter(edge_list), end; python::stl_input_iterator<python::object> iter(edge_list), end;
for (; iter != end; ++iter) for (; iter != end; ++iter)
{ {
const auto& row = *iter; const auto& row = *iter;
python::stl_input_iterator<python::object> eiter(row), eend; python::stl_input_iterator<python::object> eiter(row), eend;
size_t s = 0; size_t s = 0;
size_t t = 0;
typename graph_traits<Graph>::edge_descriptor e; typename graph_traits<Graph>::edge_descriptor e;
size_t i = 0; size_t i = 0;
for(; eiter != eend; ++eiter) for(; eiter != eend; ++eiter)
...@@ -403,20 +313,23 @@ struct add_edge_list_iter ...@@ -403,20 +313,23 @@ struct add_edge_list_iter
if (i >= eprops.size() + 2) if (i >= eprops.size() + 2)
break; break;
const auto& val = *eiter; const auto& val = *eiter;
switch (i) if (i < 2)
{ {
case 0: val_t x;
s = python::extract<size_t>(val); if constexpr (std::is_same_v<val_t, python::object>)
while (s >= num_vertices(g)) x = val;
else
x = python::extract<val_t>(val);
auto v = get_vertex(x);
while (v >= num_vertices(g))
add_vertex(g); add_vertex(g);
break; if (i == 0)
case 1: s = v;
t = python::extract<size_t>(val); else
while (t >= num_vertices(g)) e = add_edge(s, v, g).first;
add_vertex(g); }
e = add_edge(vertex(s, g), vertex(t, g), g).first; else
break; {
default:
try try
{ {
put(eprops[i - 2], e, val); put(eprops[i - 2], e, val);
...@@ -430,20 +343,22 @@ struct add_edge_list_iter ...@@ -430,20 +343,22 @@ struct add_edge_list_iter
i++; i++;
} }
} }
} };
}; };
void do_add_edge_list_iter(GraphInterface& gi, python::object edge_list, void do_add_edge_list_hashed(GraphInterface& gi, python::object aedge_list,
python::object eprops) boost::any& vertex_map,
python::object eprops)
{ {
run_action<>() run_action<graph_tool::all_graph_views, boost::mpl::true_>()
(gi, (gi,
[&](auto&& graph) [&](auto&& graph, auto&& a2)
{ {
return add_edge_list_iter() return add_edge_list_hash()
(std::forward<decltype(graph)>(graph), edge_list, eprops); (std::forward<decltype(graph)>(graph), aedge_list,
})(); std::forward<decltype(a2)>(a2), eprops);
},
writable_vertex_properties())(vertex_map);
} }
} // namespace graph_tool } // namespace graph_tool
...@@ -2407,11 +2407,11 @@ class Graph(object): ...@@ -2407,11 +2407,11 @@ class Graph(object):
""" """
return libcore.remove_edge(self.__graph, edge) return libcore.remove_edge(self.__graph, edge)
def add_edge_list(self, edge_list, hashed=False, string_vals=False, def add_edge_list(self, edge_list, hashed=False, hash_type="string",
eprops=None): eprops=None):
"""Add a list of edges to the graph, given by ``edge_list``, which can """Add a list of edges to the graph, given by ``edge_list``, which can
be an iterator of ``(source, target)`` pairs where both ``source`` and be an iterator of ``(source, target)`` pairs where both ``source`` and
``target`` are vertex indexes, or a :class:`~numpy.ndarray` of shape ``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 ``(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 ``(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.
...@@ -2420,10 +2420,11 @@ class Graph(object): ...@@ -2420,10 +2420,11 @@ class Graph(object):
are not assumed to correspond to vertex indices directly. In this case are not assumed to correspond to vertex indices directly. In this case
they will be mapped to vertex indices according to the order in which they will be mapped to vertex indices according to the order in which
they are encountered, and a vertex property map with the vertex values they are encountered, and a vertex property map with the vertex values
is returned. If ``string_vals == True``, the algorithm assumes that the is returned. The option ``hash_type`` will determine the expected type
vertex values are strings. Otherwise, they will be assumed to be numeric used by the hash keys, and they can be any property map value type (see
if ``edge_list`` is a :class:`~numpy.ndarray`, or arbitrary python :class:`PropertyMap`), unless ``edge_list`` is a :class:`numpy.ndarray`,
objects if it is not. in which case the value of this option is ignored, and the type is
determined automatically.