Commit 83186f00 authored by Tiago Peixoto's avatar Tiago Peixoto

Implement support for edge properties in Graph.add_edge_list()

parent 2bc9ddf4
......@@ -429,9 +429,9 @@ private:
virtual void put(const Key& k, const Value& val)
{
return put_dispatch(_pmap, k, _c_put(val),
is_convertible<typename boost::property_traits<PropertyMap>::category,
boost::writable_property_map_tag>());
put_dispatch(_pmap, k, _c_put(val),
is_convertible<typename boost::property_traits<PropertyMap>::category,
boost::writable_property_map_tag>());
}
template <class PMap>
......
......@@ -387,17 +387,21 @@ template <class ValueList>
struct add_edge_list
{
template <class Graph>
void operator()(Graph& g, python::object aedge_list, bool& found) const
void operator()(Graph& g, python::object aedge_list,
python::object& eprops, bool& found) const
{
boost::mpl::for_each<ValueList>(std::bind(dispatch(), std::ref(g),
std::ref(aedge_list),
std::ref(found), placeholders::_1));
std::ref(eprops),
std::ref(found),
placeholders::_1));
}
struct dispatch
{
template <class Graph, class Value>
void operator()(Graph& g, python::object& aedge_list, bool& found, Value) const
void operator()(Graph& g, python::object& aedge_list,
python::object& oeprops, bool& found, Value) const
{
if (found)
return;
......@@ -408,13 +412,31 @@ struct add_edge_list
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());
for (const auto& e : edge_list)
{
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);
auto ne = add_edge(vertex(s, g), vertex(t, g), g).first;
for (size_t i = 0; i < e.size() - 2; ++i)
{
try
{
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;
}
......@@ -423,13 +445,15 @@ struct add_edge_list
};
};
void do_add_edge_list(GraphInterface& gi, python::object aedge_list)
void do_add_edge_list(GraphInterface& gi, python::object aedge_list,
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<>()(gi, std::bind(add_edge_list<vals_t>(), placeholders::_1, aedge_list,
run_action<>()(gi, std::bind(add_edge_list<vals_t>(), placeholders::_1,
aedge_list, std::ref(eprops),
std::ref(found)))();
if (!found)
throw GraphException("Invalid type for edge list; must be two-dimensional with a scalar type");
......@@ -440,17 +464,18 @@ 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
bool& found, bool use_str, python::object& eprops) const
{
boost::mpl::for_each<ValueList>(std::bind(dispatch(), std::ref(g),
std::ref(aedge_list), std::ref(vmap),
std::ref(found), placeholders::_1));
std::ref(found), std::ref(eprops),
placeholders::_1));
if (!found)
{
if (use_str)
dispatch()(g, aedge_list, vmap, found, std::string());
dispatch()(g, aedge_list, vmap, found, eprops, std::string());
else
dispatch()(g, aedge_list, vmap, found, python::object());
dispatch()(g, aedge_list, vmap, found, eprops, python::object());
}
}
......@@ -458,7 +483,7 @@ struct add_edge_list_hash
{
template <class Graph, class VProp, class Value>
void operator()(Graph& g, python::object& aedge_list, VProp& vmap,
bool& found, Value) const
bool& found, python::object& oeprops, Value) const
{
if (found)
return;
......@@ -470,6 +495,11 @@ struct add_edge_list_hash
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());
auto get_vertex = [&] (const Value& r) -> size_t
{
......@@ -488,7 +518,19 @@ struct add_edge_list_hash
{
size_t s = get_vertex(e[0]);
size_t t = get_vertex(e[1]);
add_edge(vertex(s, g), vertex(t, g), g);
auto ne = add_edge(vertex(s, g), vertex(t, g), g).first;
for (size_t i = 0; i < e.size() - 2; ++i)
{
try
{
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;
}
......@@ -497,7 +539,7 @@ struct add_edge_list_hash
template <class Graph, class VProp>
void operator()(Graph& g, python::object& edge_list, VProp& vmap,
bool& found, std::string) const
bool& found, python::object& oeprops, std::string) const
{
if (found)
return;
......@@ -505,6 +547,12 @@ struct add_edge_list_hash
{
unordered_map<std::string, 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 std::string& r) -> size_t
{
auto iter = vertices.find(r);
......@@ -521,10 +569,46 @@ struct add_edge_list_hash
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);
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(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;
}
......@@ -533,7 +617,7 @@ struct add_edge_list_hash
template <class Graph, class VProp>
void operator()(Graph& g, python::object& edge_list, VProp& vmap,
bool& found, python::object) const
bool& found, python::object& oeprops, python::object) const
{
if (found)
return;
......@@ -541,6 +625,12 @@ struct add_edge_list_hash
{
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);
......@@ -557,10 +647,46 @@ struct add_edge_list_hash
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);
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++;
}
}
found = true;
}
......@@ -570,7 +696,8 @@ struct add_edge_list_hash
};
void do_add_edge_list_hashed(GraphInterface& gi, python::object aedge_list,
boost::any& vertex_map, bool is_str)
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,
......@@ -579,10 +706,75 @@ void do_add_edge_list_hashed(GraphInterface& gi, python::object aedge_list,
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),
is_str, std::ref(eprops)),
writable_vertex_properties())(vertex_map);
if (!found)
throw GraphException("Invalid type for edge list; must be two-dimensional with a scalar or string type");
}
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());
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)
{
if (i >= eprops.size() + 2)
break;
const auto& val = *eiter;
switch (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
{
put(eprops[i - 2], e, val);
}
catch(bad_lexical_cast&)
{
throw ValueException("Invalid edge property value: " +
python::extract<string>(python::str(val))());
}
}
i++;
}
}
}
};
void do_add_edge_list_iter(GraphInterface& gi, python::object edge_list,
python::object eprops)
{
run_action<>()
(gi, std::bind(add_edge_list_iter(), placeholders::_1,
std::ref(edge_list), std::ref(eprops)))();
}
......@@ -640,6 +832,7 @@ void export_python_interface()
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("add_edge_list_iter", graph_tool::do_add_edge_list_iter);
def("get_edge", get_edge);
def("get_vertex_index", get_vertex_index);
......
......@@ -239,16 +239,20 @@ def _gt_type(obj):
return "vector<%s>" % _gt_type(obj[0])
return "object"
def _convert(prop, val):
def _converter(val_type):
# attempt to convert to a compatible python type. This is useful,
# for instance, when dealing with numpy types.
vtype = _python_type(prop.value_type())
vtype = _python_type(val_type)
if type(vtype) is tuple:
return [vtype[1](x) for x in val]
if vtype is object:
return val
return vtype(val)
def convert(val):
return [vtype[1](x) for x in val]
elif vtype is object:
def convert(val):
return val
else:
def convert(val):
return vtype(val)
return convert
def show_config():
......@@ -421,6 +425,7 @@ class PropertyMap(object):
except NameError:
pass # ignore if GraphView is yet undefined
self.__key_type = key_type
self.__convert = _converter(self.value_type())
self.__register_map()
def __key_trans(self, key):
......@@ -482,14 +487,14 @@ class PropertyMap(object):
try:
self.__map[key] = v
except TypeError:
self.__map[key] = _convert(self, v)
self.__map[key] = self.__convert(v)
except ArgumentError:
try:
key = self.__key_convert(key)
try:
self.__map[key] = v
except TypeError:
self.__map[key] = _convert(self, v)
self.__map[key] = self.__convert(v)
except ArgumentError:
if self.key_type() == "e":
kt = "Edge"
......@@ -829,11 +834,11 @@ class PropertyMap(object):
else:
u = GraphView(g, skip_vfilt=True, skip_efilt=True)
if key_type == "v":
vals = [_convert(self, self[v]) for v in u.vertices()]
vals = [self.__convert(self[v]) for v in u.vertices()]
elif key_type == "e":
vals = [_convert(self, self[e]) for e in u.edges()]
vals = [self.__convert(self[e]) for e in u.edges()]
else:
vals = _convert(self, self[g])
vals = self.__convert(self[g])
state = dict(g=g, value_type=value_type,
key_type=key_type, vals=vals,
......@@ -1885,9 +1890,10 @@ class Graph(object):
self.__check_perms("del_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, string_vals=False,
eprops=None):
"""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
be an iterator 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
......@@ -1895,17 +1901,33 @@ class Graph(object):
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 will be mapped to vertex indices 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.
If given, `eprops` specifies edge property maps that will be filled with
the remaining values at each row, if there are more than two.
"""
self.__check_perms("add_edge")
if eprops is None:
eprops = ()
else:
convert = [_converter(x.value_type()) for x in eprops]
eprops = [_prop("e", self, x) for x in eprops]
if not isinstance(edge_list, numpy.ndarray):
def wrap(elist):
for row in elist:
yield (val if i < 2 else convert[i - 2](val)
for (i, val) in enumerate(row))
edge_list = wrap(edge_list)
if not hashed:
edges = numpy.asarray(edge_list)
libcore.add_edge_list(self.__graph, edges)
if isinstance(edge_list, numpy.ndarray):
libcore.add_edge_list(self.__graph, edge_list, eprops)
else:
libcore.add_edge_list_iter(self.__graph, edge_list, eprops)
else:
if isinstance(edge_list, numpy.ndarray):
vprop = self.new_vertex_property(_gt_type(edge_list.dtype))
......@@ -1915,7 +1937,7 @@ class Graph(object):
vprop = self.new_vertex_property("object")
libcore.add_edge_list_hashed(self.__graph, edge_list,
_prop("v", self, vprop),
string_vals)
string_vals, eprops)
return vprop
def set_fast_edge_removal(self, fast=True):
......
......@@ -42,7 +42,7 @@ from __future__ import division, absolute_import, print_function
from .. dl_import import dl_import
dl_import("from . import libgraph_tool_util")
from .. import _degree, _prop, _convert
from .. import _degree, _prop, _converter
import weakref
__all__ = ["find_vertex", "find_vertex_range", "find_edge", "find_edge_range"]
......@@ -52,7 +52,7 @@ def find_vertex(g, prop, match):
"""Find all vertices `v` for which `prop[v] = match`. The parameter prop
can be either a :class:`~graph_tool.PropertyMap` or string with value "in",
"out" or "total", representing a degree type."""
val = _convert(prop, match)
val = _converter(prop.value_type())(match)
ret = libgraph_tool_util.\
find_vertex_range(weakref.ref(g), _degree(g, prop),
(val, val))
......@@ -72,7 +72,7 @@ def find_vertex_range(g, prop, range):
def find_edge(g, prop, match):
"""Find all edges `e` for which `prop[e] = match`. The parameter prop
must be a :class:`~graph_tool.PropertyMap`."""
val = _convert(prop, match)
val = _converter(prop.value_type())(match)
ret = libgraph_tool_util.\
find_edge_range(weakref.ref(g), _prop("e", g, prop),
(val, val))
......@@ -82,7 +82,8 @@ def find_edge(g, prop, match):
def find_edge_range(g, prop, range):
"""Find all edges `e` for which `range[0] <= prop[e] <= range[1]`. The
parameter prop can be either a :class:`~graph_tool.PropertyMap`."""
convert = converter(prop.value_type())
ret = libgraph_tool_util.\
find_edge_range(weakref.ref(g), _prop("e", g, prop),
(_convert(prop, range[0]), _convert(prop, range[1])))
(convert(range[0]), convert(range[1])))
return ret
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