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

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,
python::object eprops);
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);
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,
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>
void operator()(Graph& g, python::object aedge_list, VProp vmap,
bool& found, bool use_str, python::object& eprops) const
template <class Graph>
void operator()(Graph& g, python::object& edge_list,
python::object& oeprops) const
{
boost::mpl::for_each<ValueList>(std::bind(dispatch(), std::ref(g),
std::ref(aedge_list), std::ref(vmap),
std::ref(found), std::ref(eprops),
std::placeholders::_1));
if (!found)
{
if (use_str)
dispatch()(g, aedge_list, vmap, found, eprops, std::string());
else
dispatch()(g, aedge_list, vmap, found, eprops, python::object());
}
}
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());
struct dispatch
{
template <class Graph, class VProp, class Value>
void operator()(Graph& g, python::object& aedge_list, VProp& vmap,
bool& found, python::object& oeprops, Value) const
python::stl_input_iterator<python::object> iter(edge_list), end;
for (; iter != end; ++iter)
{
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");
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);
const auto& row = *iter;
python::stl_input_iterator<python::object> eiter(row), eend;
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;
};
size_t s = 0;
size_t t = 0;
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]);
size_t t = get_vertex(e[1]);
auto ne = add_edge(vertex(s, g), vertex(t, g), g).first;
for (size_t i = 0; i < n_props; ++i)
case 0:
s = python::extract<size_t>(val);
while (s >= num_vertices(g))
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], ne, e[i + 2]);
}
catch(bad_lexical_cast&)
{
throw ValueException("Invalid edge property value: " +
lexical_cast<string>(e[i + 2]));
}
put(eprops[i - 2], e, val);
}
catch(bad_lexical_cast&)
{
throw ValueException("Invalid edge property value: " +
python::extract<string>(python::str(val))());
}
}
found = true;
i++;
}
catch (InvalidNumpyConversion& e) {}
}
}
};
template <class Graph, class VProp>
void operator()(Graph& g, python::object& edge_list, VProp& vmap,
bool& found, python::object& oeprops, std::string) const
void do_add_edge_list_iter(GraphInterface& gi, python::object edge_list,
python::object eprops)
{
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
{
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;
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());
template <class Graph, class VProp>
void numpy_dispatch(Graph& g, python::object& aedge_list, VProp& vmap,
python::object& oeprops) const
{
typedef typename property_traits<VProp>::value_type val_t;
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;
};
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>,
gt_hash_map<val_t, size_t>,
unordered_map<val_t, size_t>> vmap_t;
vmap_t vertices;
python::stl_input_iterator<python::object> iter(edge_list), end;
for (; iter != end; ++iter)
{
const auto& row = *iter;
if (edge_list.shape()[1] < 2)
throw GraphException("Second dimension in edge list must be of size (at least) two");
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 t = 0;
size_t n_props = std::min(eprops.size(), edge_list.shape()[1] - 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(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++;
}
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;
}
found = true;
}
catch (InvalidNumpyConversion& e) {}
}
return iter->second;
};
template <class Graph, class VProp>
void operator()(Graph& g, python::object& edge_list, VProp& vmap,
bool& found, python::object& oeprops, python::object) const
for (const auto& e : edge_list)
{
if (found)
return;
try
size_t s = get_vertex(e[0]);
size_t t = get_vertex(e[1]);
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;
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)
try
{
const auto& row = *iter;
python::stl_input_iterator<python::object> eiter(row), eend;
size_t s = 0;
size_t t = 0;
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++;
}
put(eprops[i], ne, e[i + 2]);
}
catch(bad_lexical_cast&)
{
throw ValueException("Invalid edge property value: " +
lexical_cast<string>(e[i + 2]));
}
found = true;
}
catch (InvalidNumpyConversion& e) {}
}
};
};
}
void do_add_edge_list_hashed(GraphInterface& gi, python::object aedge_list,
boost::any& vertex_map, bool is_str,
python::object eprops)
{
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::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);
}
template <class Graph, class VProp>
void dispatch(Graph& g, python::object& edge_list, VProp& vmap,
python::object& oeprops) const
{
typedef typename property_traits<VProp>::value_type val_t;
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;
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 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;
for (; iter != end; ++iter)
{
const auto& row = *iter;
python::stl_input_iterator<python::object> eiter(row), eend;
size_t s = 0;
size_t t = 0;
typename graph_traits<Graph>::edge_descriptor e;
size_t i = 0;
for(; eiter != eend; ++eiter)
......@@ -403,20 +313,23 @@ struct add_edge_list_iter
if (i >= eprops.size() + 2)
break;
const auto& val = *eiter;
switch (i)
if (i < 2)
{
case 0:
s = python::extract<size_t>(val);
while (s >= num_vertices(g))
val_t x;
if constexpr (std::is_same_v<val_t, python::object>)
x = val;
else
x = python::extract<val_t>(val);
auto v = get_vertex(x);
while (v >= num_vertices(g))
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:
if (i == 0)
s = v;
else
e = add_edge(s, v, g).first;
}
else
{
try
{
put(eprops[i - 2], e, val);
......@@ -430,20 +343,22 @@ struct add_edge_list_iter
i++;
}
}
}
};
};
void do_add_edge_list_iter(GraphInterface& gi, python::object edge_list,
python::object eprops)
void do_add_edge_list_hashed(GraphInterface& gi, python::object aedge_list,
boost::any& vertex_map,
python::object eprops)
{
run_action<>()
run_action<graph_tool::all_graph_views, boost::mpl::true_>()
(gi,
[&](auto&& graph)
[&](auto&& graph, auto&& a2)
{
return add_edge_list_iter()
(std::forward<decltype(graph)>(graph), edge_list, eprops);
})();
return add_edge_list_hash()
(std::forward<decltype(graph)>(graph), aedge_list,
std::forward<decltype(a2)>(a2), eprops);
},
writable_vertex_properties())(vertex_map);
}
} // namespace graph_tool
......@@ -2407,11 +2407,11 @@ class Graph(object):
"""
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):
"""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
``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
``(source, target)`` pair. If the list references vertices which do not
exist in the graph, they will be created.
......@@ -2420,10 +2420,11 @@ class Graph(object):
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 are encountered, and 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.
is returned. The option ``hash_type`` will determine the expected type
used by the hash keys, and they can be any property map value type (see
:class:`PropertyMap`), unless ``edge_list`` is a :class:`numpy.ndarray`,
in which case the value of this option is ignored, and the type is
determined automatically.
If given, ``eprops`` should specify an iterable containing edge property
maps that will be filled with the remaining values at each row, if there
......@@ -2465,13 +2466,10 @@ class Graph(object):
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")
vprop = self.new_vertex_property(hash_type)
libcore.add_edge_list_hashed(self.__graph, edge_list,
_prop("v", self, vprop),
string_vals, eprops)
_prop("v", self, vprop), eprops)
return vprop
def set_fast_edge_removal(self, fast=True):
......
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