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

Split PropertyMap into Vertex/Edge/GraphPropertyMap

This improves performance of the Python API.
parent d04f8187
......@@ -198,6 +198,12 @@
.. autoclass:: Vertex
.. autoclass:: Edge
.. autoclass:: PropertyMap
.. autoclass:: VertexPropertyMap
:show-inheritance:
.. autoclass:: EdgePropertyMap
:show-inheritance:
.. autoclass:: GraphPropertyMap
:show-inheritance:
.. autoclass:: PropertyArray
:show-inheritance:
:no-members:
......
......@@ -373,10 +373,11 @@ Property maps
Property maps are a way of associating additional information to the
vertices, edges or to the graph itself. There are thus three types of
property maps: vertex, edge and graph. All of them are handled by the
same class, :class:`~graph_tool.PropertyMap`. Each created property map
has an associated *value type*, which must be chosen from the predefined
set:
property maps: vertex, edge and graph. They are handled by the
classes :class:`~graph_tool.VertexPropertyMap`,
:class:`~graph_tool.EdgePropertyMap`, and
:class:`~graph_tool.GraphPropertyMap`. Each created property map has an
associated *value type*, which must be chosen from the predefined set:
.. tabularcolumns:: |l|l|
......@@ -402,10 +403,15 @@ set:
``python::object`` ``object``
======================== ======================
New property maps can be created for a given graph by calling the
:meth:`~graph_tool.Graph.new_vertex_property`, :meth:`~graph_tool.Graph.new_edge_property`, or
:meth:`~graph_tool.Graph.new_graph_property`, for each map type. The values are then
accessed by vertex or edge descriptors, or the graph itself, as such:
New property maps can be created for a given graph by calling one of the
methods :meth:`~graph_tool.Graph.new_vertex_property` (alias
:meth:`~graph_tool.Graph.new_vp`),
:meth:`~graph_tool.Graph.new_edge_property` (alias
:meth:`~graph_tool.Graph.new_ep`), or
:meth:`~graph_tool.Graph.new_graph_property` (alias
:meth:`~graph_tool.Graph.new_gp`), for each map type. The values are
then accessed by vertex or edge descriptors, or the graph itself, as
such:
.. doctest::
......@@ -414,26 +420,30 @@ accessed by vertex or edge descriptors, or the graph itself, as such:
g = Graph()
g.add_vertex(100)
# insert some random links
for s,t in izip(randint(0, 100, 100), randint(0, 100, 100)):
g.add_edge(g.vertex(s), g.vertex(t))
vprop_double = g.new_vertex_property("double") # Double-precision floating point
vprop_double[g.vertex(10)] = 3.1416
v = g.vertex(10)
vprop_double[v] = 3.1416
vprop_vint = g.new_vertex_property("vector<int>") # Vector of ints
vprop_vint[g.vertex(40)] = [1, 3, 42, 54]
v = g.vertex(40)
vprop_vint[v] = [1, 3, 42, 54]
eprop_dict = g.new_edge_property("object") # Arbitrary python object.
eprop_dict[g.edges().next()] = {"foo": "bar", "gnu": 42} # In this case, a dict.
e = g.edges().next()
eprop_dict[e] = {"foo": "bar", "gnu": 42} # In this case, a dict.
gprop_bool = g.new_graph_property("bool") # Boolean
gprop_bool = g.new_graph_property("bool") # Boolean
gprop_bool[g] = True
Property maps with scalar value types can also be accessed as a
:class:`numpy.ndarray`, with the
:meth:`~graph_tool.PropertyMap.get_array` method, or the
:attr:`~graph_tool.PropertyMap.a` attribute, i.e.,
:attr:`~graph_tool.PropertyMap.a` attribute, e.g.,
.. doctest::
......
......@@ -257,35 +257,15 @@ struct convert
{
Type1 operator()(const Type2& v) const
{
return do_convert(v, std::is_convertible<Type2,Type1>());
}
Type1 do_convert(const Type2& v, std::true_type) const
{
return Type1(v);
}
Type1 do_convert(const Type2& v, std::false_type) const
{
return specific_convert<Type1,Type2>()(v);
}
template <class T1, class T2>
struct specific_convert
{
T1 operator()(const T2&) const
if constexpr (std::is_same<Type1, boost::python::object>::value)
{
throw boost::bad_lexical_cast(); // default action
return boost::python::object(v);
}
};
// specific specializations
// python::object
template <class T1>
struct specific_convert<T1,boost::python::object>
{
T1 operator()(const boost::python::object& v) const
else if constexpr (std::is_convertible<Type2,Type1>::value)
{
return Type1(v);
}
else if constexpr (std::is_same<Type2, boost::python::object>::value)
{
boost::python::extract<Type1> x(v);
if (x.check())
......@@ -293,35 +273,40 @@ struct convert
else
throw boost::bad_lexical_cast();
}
};
// std::string
template <class T1>
struct specific_convert<T1,std::string>
{
T1 operator()(const std::string& v) const
else if constexpr (std::is_same<Type2, std::string>::value)
{
//uint8_t is not char, it is bool!
if (std::is_same<T1, uint8_t>::value)
return convert<T1,int>()(boost::lexical_cast<int>(v));
if (std::is_same<Type1, uint8_t>::value)
return convert<Type1,int>()(boost::lexical_cast<int>(v));
else
return boost::lexical_cast<T1>(v);
return boost::lexical_cast<Type1>(v);
}
};
template <class T2>
struct specific_convert<std::string,T2>
{
std::string operator()(const T2& v) const
else if constexpr (std::is_same<Type1, std::string>::value)
{
//uint8_t is not char, it is bool!
if (std::is_same<T2, uint8_t>::value)
return boost::lexical_cast<std::string>(convert<int,T2>()(v));
if (std::is_same<Type2, uint8_t>::value)
return boost::lexical_cast<std::string>(convert<int,Type2>()(v));
else
return boost::lexical_cast<std::string>(v);
}
else
{
return specific_convert<Type1, Type2>()(v);
}
}
// default action
template <class T1, class T2>
struct specific_convert
{
T1 operator()(const T2&) const
{
throw boost::bad_lexical_cast();
}
};
// specific specializations
// vectors
template <class T1, class T2>
struct specific_convert<std::vector<T1>, std::vector<T2>>
......@@ -338,20 +323,6 @@ struct convert
};
// python::object to std::string, to solve ambiguity
template<> template<>
struct convert<std::string,boost::python::object>::specific_convert<std::string,boost::python::object>
{
std::string operator()(const boost::python::object& v) const
{
boost::python::extract<std::string> x(v);
if (x.check())
return x();
else
throw boost::bad_lexical_cast();
}
};
// No op
template <class Type1>
struct convert<Type1, Type1>
......@@ -435,30 +406,30 @@ private:
}
template <class PMap>
Value get_dispatch(PMap pmap, const typename boost::property_traits<PMap>::key_type& k,
Value get_dispatch(PMap&& pmap, const typename boost::property_traits<std::remove_reference_t<PMap>>::key_type& k,
std::true_type)
{
return _c_get(boost::get(pmap, k));
}
template <class PMap>
Value get_dispatch(PMap, const typename boost::property_traits<PMap>::key_type&,
Value get_dispatch(PMap&&, const typename boost::property_traits<std::remove_reference_t<PMap>>::key_type&,
std::false_type)
{
throw graph_tool::ValueException("Property map is not readable.");
}
template <class PMap>
void put_dispatch(PMap pmap, const typename boost::property_traits<PMap>::key_type& k,
typename boost::property_traits<PMap>::value_type val,
void put_dispatch(PMap&& pmap, const typename boost::property_traits<std::remove_reference_t<PMap>>::key_type& k,
typename boost::property_traits<std::remove_reference_t<PMap>>::value_type val,
std::true_type)
{
boost::put(pmap, k, val);
}
template <class PMap>
void put_dispatch(PMap, const typename boost::property_traits<PMap>::key_type&,
typename boost::property_traits<PMap>::value_type,
void put_dispatch(PMap&&, const typename boost::property_traits<std::remove_reference_t<PMap>>::key_type&,
typename boost::property_traits<std::remove_reference_t<PMap>>::value_type,
std::false_type)
{
throw ValueException("Property map is not writable.");
......
......@@ -24,6 +24,7 @@
#include <boost/python/stl_iterator.hpp>
#include <set>
#include "coroutine.hh"
using namespace std;
using namespace boost;
......@@ -327,112 +328,264 @@ 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);
template <class PMaps>
int value_type_promotion(std::vector<boost::any>& props)
{
int type_pos = boost::mpl::find<value_types,int64_t>::type::pos::value;
for (auto& aep : props)
{
gt_dispatch<>()([&](auto& ep)
{
typedef std::remove_reference_t<decltype(ep)> pmap_t;
typedef typename property_traits<pmap_t>::value_type val_t;
if (std::is_same<val_t, size_t>::value)
return;
constexpr int ep_t = boost::mpl::find<value_types,val_t>::type::pos::value;
type_pos = std::max(type_pos, ep_t);
},
PMaps())(aep);
}
return type_pos;
}
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);
}
template <int kind>
python::object get_vertex_list(GraphInterface& gi, size_t v,
python::list ovprops)
{
std::vector<boost::any> avprops;
for (int i = 0; i < python::len(ovprops); ++i)
{
avprops.push_back(python::extract<boost::any>(ovprops[i])());
if (!belongs<vertex_scalar_properties>()(avprops.back()))
throw ValueException("vertex property map must be of scalar type");
}
int vtype = boost::mpl::find<value_types,int64_t>::type::pos::value;
if (!avprops.empty())
vtype = value_type_promotion<vertex_scalar_properties>(avprops);
python::object ret;
auto dispatch =
[&](auto&& vrange)
{
mpl::for_each<scalar_types>(
[&](auto t)
{
typedef decltype(t) t_t;
if (vtype != boost::mpl::find<value_types, t_t>::type::pos::value)
return;
typedef DynamicPropertyMapWrap<t_t, GraphInterface::vertex_t>
converted_map_t;
std::vector<converted_map_t> vprops;
for (auto& aep: avprops)
vprops.emplace_back(aep, vertex_scalar_properties());
std::vector<t_t> vlist;
run_action<>()(gi,
[&](auto& g)
{
for (auto u: vrange(g))
{
vlist.push_back(u);
for (auto& vp : vprops)
vlist.push_back(get(vp, u));
}
})();
ret = wrap_vector_owned(vlist);
});
};
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);
switch (kind)
{
case 0:
dispatch([&](auto& g){return vertices_range(g);});
break;
case 1:
dispatch([&](auto& g){return out_neighbors_range(v, g);});
break;
case 2:
dispatch([&](auto& g){return in_neighbors_range(v, g);});
break;
case 3:
dispatch([&](auto& g){return all_neighbors_range(v, g);});
}
return ret;
}
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);
enum range_t { FULL, OUT, IN, ALL };
template <int kind>
python::object get_vertex_iter(GraphInterface& gi, int v, python::list ovprops)
{
#ifdef HAVE_BOOST_COROUTINE
auto dispatch = [&](auto&&vrange)
{
auto yield_dispatch = [&](auto& yield)
{
if (python::len(ovprops) == 0)
{
run_action<>()(gi,
[&](auto& g)
{
for (auto v: vertices_range(g))
yield(python::object(v));
})();
}
else
{
typedef DynamicPropertyMapWrap<python::object,
GraphInterface::vertex_t>
converted_map_t;
std::vector<converted_map_t> vprops;
for (int i = 0; i < python::len(ovprops); ++i)
vprops.emplace_back(python::extract<boost::any>(ovprops[i])(),
vertex_properties());
run_action<>()(gi,
[&](auto& g)
{
for (auto v: vrange(g))
{
python::list vlist;
vlist.append(python::object(v));
for (auto& vp : vprops)
vlist.append(get(vp, v));
yield(vlist);
}
})();
}
};
return boost::python::object(CoroGenerator(yield_dispatch));
};
switch (kind)
{
case range_t::FULL:
return dispatch([&](auto& g){return vertices_range(g);});
case range_t::OUT:
return dispatch([&](auto& g){return out_neighbors_range(v, g);});
case range_t::IN:
return dispatch([&](auto& g){return in_neighbors_range(v, g);});
case range_t::ALL:
return dispatch([&](auto& g){return all_neighbors_range(v, g);});
}
#else
throw GraphException("This functionality is not available because boost::coroutine was not found at compile-time");
#endif
}
python::object get_out_neighbors_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_neighbors_range(v, g))
vlist.push_back(u);
})();
return wrap_vector_owned(vlist);
template <int kind>
python::object get_edge_list(GraphInterface& gi, size_t v, python::list oeprops)
{
std::vector<boost::any> aeprops;
for (int i = 0; i < python::len(oeprops); ++i)
{
aeprops.push_back(python::extract<boost::any>(oeprops[i])());
if (!belongs<edge_scalar_properties>()(aeprops.back()))
throw ValueException("edge property map must be of scalar type");
}
int etype = boost::mpl::find<value_types,int64_t>::type::pos::value;
if (!aeprops.empty())
etype = value_type_promotion<edge_scalar_properties>(aeprops);
python::object ret;
auto dispatch =
[&](auto&& erange)
{
mpl::for_each<scalar_types>(
[&](auto t)
{
typedef decltype(t) t_t;
if (etype != boost::mpl::find<value_types, t_t>::type::pos::value)
return;
typedef DynamicPropertyMapWrap<t_t, GraphInterface::edge_t>
converted_map_t;
std::vector<converted_map_t> eprops;
for (auto& aep: aeprops)
eprops.emplace_back(aep, edge_scalar_properties());
std::vector<t_t> elist;
run_action<>()(gi,
[&](auto& g)
{
for (auto e: erange(g))
{
elist.push_back(source(e, g));
elist.push_back(target(e, g));
for (auto& ep : eprops)
elist.push_back(get(ep,e));
}
})();
ret = wrap_vector_owned(elist);
});
};
switch (kind)
{
case range_t::FULL:
dispatch([&](auto& g){return edges_range(g);});
break;
case range_t::OUT:
dispatch([&](auto& g){return out_edges_range(v, g);});
break;
case range_t::IN:
dispatch([&](auto& g){return in_edges_range(v, g);});
break;
case range_t::ALL:
dispatch([&](auto& g){return all_edges_range(v, g);});
}
return ret;
}
python::object get_in_neighbors_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_neighbors_range(v, g))
vlist.push_back(u);
})();
return wrap_vector_owned(vlist);
template <int kind>
python::object get_edge_iter(GraphInterface& gi, size_t v, python::list oeprops)
{
#ifdef HAVE_BOOST_COROUTINE
auto dispatch = [&](auto&&erange)
{
auto yield_dispatch = [&](auto& yield)
{
typedef DynamicPropertyMapWrap<python::object,
GraphInterface::edge_t>
converted_map_t;
std::vector<converted_map_t> eprops;
for (int i = 0; i < python::len(oeprops); ++i)
eprops.emplace_back(python::extract<boost::any>(oeprops[i])(),
edge_properties());
run_action<>()(gi,
[&](auto& g)
{
for (auto e: erange(g))
{
python::list elist;
elist.append(python::object(source(e, g)));
elist.append(python::object(target(e, g)));
for (auto& ep : eprops)
elist.append(get(ep, e));
yield(elist);
}
})();
};
return boost::python::object(CoroGenerator(yield_dispatch));
};
switch (kind)
{
case range_t::FULL:
return dispatch([&](auto& g){return edges_range(g);});
case range_t::OUT:
return dispatch([&](auto& g){return out_edges_range(v, g);});
case range_t::IN:
return dispatch([&](auto& g){return in_edges_range(v, g);});
case range_t::ALL:
return dispatch([&](auto& g){return all_edges_range(v, g);});
}