Commit 0b66e272 authored by Tiago Peixoto's avatar Tiago Peixoto

Refactor metaprogramming engine

This is a huge commit which completely refactors the metaprogramming
engine which generates and selects (at run time) the graph view type and
the desired algorithm implementation (template instantiation) that runs
on it.

Things are laid out now as following. There exists a main underlying
graph type (GraphInterface::multigraph_t) and several other template
classes that mask it some way or another, in a hierarchic fashion:

     multigraph_t -> filtered_graph (edges only, vertices only, both)
         |                               |           |           |
         |                               |           |           |
         |-------(reversed_graph)--------|-----------|-----------|
         |                               |           |           |
         \------(UndirectedAdaptor)------------------------------/

The filtered_graph filters out edges and/or vertices from the graph
based on some scalar boolean property. The reversed_graph reversed the
direction of the edges and, finally, the UndirectedAdaptor treats the
original directed graphs as undirected, transversing the in- and
out-edges of each vertex indifferently. Thus, the total number of graph
view types is 12. (The option --disable-graph-filtering can be passed to
the configure script, which will disable graph filtering altogether and
bring the total number down to 3, to reduce compile time and memory
usage)

In general, some specific algorithm, implemented as a template function
object, needs to be instantiated for each of those types. Furthermore,
the algorithm may also depend on other types, such as specific
property_maps. Thus, the following scheme is used:

    struct my_algorithm // algorithm to be implemented
    {
        template <class Graph, class PropertyMap>
        void operator()(Graph *g, PropertyMap p, double& result) const
        {
            // ...
        }
    };

    // in order for the above code to be instantiated at compile time
    // and selected at run time, the run_action template function object
    // is used from a member function of the GraphInterface class:

    double GraphInterface::MyAlgorithm(string prop_name)
    {
        double result;
        boost::any vprop = prop(property, _vertex_index, _properties);
        run_action<>()(*this, bind<void>(my_algorithm(), _1, _2,
                                         var(result)),
                       vertex_scalar_properties())(vprop);
        return result;
    }

The whole code was changed to reflect this scheme, but now things are
more centralized and less ad-hoc code needed to be
written. Unfortunately, due to GCC's high memory usage during template
instantiations, some of the code (namely all the degree correlation
things) had to be split in multiple compilation units... Maybe this will
change in the future if GCC gets optimized.

This commit also touches other parts of code. More specifically, the way
filtering gets done is very different. Now we only filter on boolean
properties, and with the above scheme, the desired implementation runs
with the correct chosen type, and no implicit type conversions should
ever happen, which would have a bad impact on performance.
parent 1e9d11d1
dnl Process this file with autoconf to produce a configure script.
AC_INIT(graph-tool, 1.2.0devel, [http://graph-tool.forked.de])
dnl graph-tool package version number
dnl An odd micro number indicates in-progress development.
dnl An even micro number indicates a released version.
m4_define(graph_tool_version_major, 1)
m4_define(graph_tool_version_minor, 9)
m4_define(graph_tool_version_micro, 0)
AC_INIT([graph-tool],
[graph_tool_version_major().graph_tool_version_minor().graph_tool_version_micro()],
[http://graph-tool.forked.de])
GRAPH_TOOL_VERSION_MAJOR=graph_tool_version_major()
GRAPH_TOOL_VERSION_MINOR=graph_tool_version_minor()
GRAPH_TOOL_VERSION_MICRO=graph_tool_version_micro()
AC_SUBST(GRAPH_TOOL_VERSION_MAJOR)
AC_SUBST(GRAPH_TOOL_VERSION_MINOR)
AC_SUBST(GRAPH_TOOL_VERSION_MICRO)
AC_CONFIG_SRCDIR(src/graph-tool)
AM_INIT_AUTOMAKE
AM_PROG_CC_C_O
......@@ -74,19 +91,19 @@ AC_ARG_ENABLE([visibility], [AC_HELP_STRING([--disable-visibility],
)
AC_MSG_CHECKING(whether to enable graph range filtering...)
AC_ARG_ENABLE([range-filtering], [AC_HELP_STRING([--disable-range-filtering],
[disable range filtering [default=enabled] ])],
AC_MSG_CHECKING(whether to enable graph filtering...)
AC_ARG_ENABLE([graph-filtering], [AC_HELP_STRING([--disable-graph-filtering],
[disable graph filtering [default=enabled] ])],
if test $enableval = no; then
[AC_MSG_RESULT(no)]
[AC_DEFINE([NO_RANGE_FILTERING], 1, [disable range filtering])]
NO_RANGE_FILTERING=yes
[AC_DEFINE([NO_GRAPH_FILTERING], 1, [disable graph filtering])]
NO_GRAPH_FILTERING=yes
else
[AC_MSG_RESULT(yes)]
fi
,
[AC_MSG_RESULT(yes)]
NO_RANGE_FILTERING=no
NO_GRAPH_FILTERING=no
)
AC_MSG_CHECKING(whether to enable parallel algorithms with openmp...)
......@@ -126,8 +143,8 @@ dnl AX_BOOST_PYTHON
dnl expat
AC_CHECK_LIB(expat,main)
AM_PATH_PYTHON([2.4])
AC_PYTHON_DEVEL(>= '2.4')
AM_PATH_PYTHON([2.5])
AC_PYTHON_DEVEL(>= '2.5')
dnl Checks for header files.
AC_CHECK_HEADER([expat.h])
......
......@@ -2,7 +2,7 @@
# NOTE: Changing this file will not affect anything until you rerun configure.
#
# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005, 2006,
# 2007 Free Software Foundation, Inc.
# 2007, 2008 Free Software Foundation, Inc.
# Originally by Gordon Matzigkeit <gord@gnu.ai.mit.edu>, 1996
#
# This program is free software; you can redistribute it and/or modify
......@@ -43,8 +43,8 @@ EXIT_FAILURE=1
PROGRAM=ltmain.sh
PACKAGE=libtool
VERSION=1.5.24
TIMESTAMP=" (1.1220.2.456 2007/06/24 02:25:32)"
VERSION=1.5.26
TIMESTAMP=" (1.1220.2.493 2008/02/01 16:58:18)"
# Be Bourne compatible (taken from Autoconf:_AS_BOURNE_COMPATIBLE).
if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
......@@ -113,15 +113,21 @@ esac
# These must not be set unconditionally because not all systems understand
# e.g. LANG=C (notably SCO).
# We save the old values to restore during execute mode.
for lt_var in LANG LC_ALL LC_CTYPE LC_COLLATE LC_MESSAGES
lt_env=
for lt_var in LANG LANGUAGE LC_ALL LC_CTYPE LC_COLLATE LC_MESSAGES
do
eval "if test \"\${$lt_var+set}\" = set; then
save_$lt_var=\$$lt_var
lt_env=\"$lt_var=\$$lt_var \$lt_env\"
$lt_var=C
export $lt_var
fi"
done
if test -n "$lt_env"; then
lt_env="env $lt_env"
fi
# Make sure IFS has a sensible default
lt_nl='
'
......@@ -499,7 +505,7 @@ do
echo "\
$PROGRAM (GNU $PACKAGE) $VERSION$TIMESTAMP
Copyright (C) 2007 Free Software Foundation, Inc.
Copyright (C) 2008 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
exit $?
......@@ -802,6 +808,7 @@ if test -z "$show_help"; then
*.for) xform=for ;;
*.java) xform=java ;;
*.obj) xform=obj ;;
*.sx) xform=sx ;;
esac
libobj=`$echo "X$libobj" | $Xsed -e "s/\.$xform$/.lo/"`
......@@ -970,7 +977,7 @@ EOF
$run $rm "$lobj" "$output_obj"
$show "$command"
if $run eval "$command"; then :
if $run eval $lt_env "$command"; then :
else
test -n "$output_obj" && $run $rm $removelist
exit $EXIT_FAILURE
......@@ -1042,7 +1049,7 @@ EOF
command="$command$suppress_output"
$run $rm "$obj" "$output_obj"
$show "$command"
if $run eval "$command"; then :
if $run eval $lt_env "$command"; then :
else
$run $rm $removelist
exit $EXIT_FAILURE
......@@ -1175,6 +1182,7 @@ EOF
thread_safe=no
vinfo=
vinfo_number=no
single_module="${wl}-single_module"
func_infer_tag $base_compile
......@@ -1660,6 +1668,11 @@ EOF
continue
;;
-multi_module)
single_module="${wl}-multi_module"
continue
;;
-module)
module=yes
continue
......@@ -2163,7 +2176,12 @@ EOF
continue
fi
name=`$echo "X$deplib" | $Xsed -e 's/^-l//'`
for searchdir in $newlib_search_path $lib_search_path $sys_lib_search_path $shlib_search_path; do
if test "$linkmode" = lib; then
searchdirs="$newlib_search_path $lib_search_path $compiler_lib_search_dirs $sys_lib_search_path $shlib_search_path"
else
searchdirs="$newlib_search_path $lib_search_path $sys_lib_search_path $shlib_search_path"
fi
for searchdir in $searchdirs; do
for search_ext in .la $std_shrext .so .a; do
# Search the libtool library
lib="$searchdir/lib${name}${search_ext}"
......@@ -2959,12 +2977,18 @@ EOF
# we do not want to link against static libs,
# but need to link against shared
eval deplibrary_names=`${SED} -n -e 's/^library_names=\(.*\)$/\1/p' $deplib`
eval deplibdir=`${SED} -n -e 's/^libdir=\(.*\)$/\1/p' $deplib`
if test -n "$deplibrary_names" ; then
for tmp in $deplibrary_names ; do
depdepl=$tmp
done
if test -f "$path/$depdepl" ; then
if test -f "$deplibdir/$depdepl" ; then
depdepl="$deplibdir/$depdepl"
elif test -f "$path/$depdepl" ; then
depdepl="$path/$depdepl"
else
# Can't find it, oh well...
depdepl=
fi
# do not add paths which are already there
case " $newlib_search_path " in
......@@ -3112,9 +3136,10 @@ EOF
case $linkmode in
oldlib)
if test -n "$deplibs"; then
$echo "$modename: warning: \`-l' and \`-L' are ignored for archives" 1>&2
fi
case " $deplibs" in
*\ -l* | *\ -L*)
$echo "$modename: warning: \`-l' and \`-L' are ignored for archives" 1>&2 ;;
esac
if test -n "$dlfiles$dlprefiles" || test "$dlself" != no; then
$echo "$modename: warning: \`-dlopen' is ignored for archives" 1>&2
......@@ -4251,9 +4276,10 @@ EOF
;;
obj)
if test -n "$deplibs"; then
$echo "$modename: warning: \`-l' and \`-L' are ignored for objects" 1>&2
fi
case " $deplibs" in
*\ -l* | *\ -L*)
$echo "$modename: warning: \`-l' and \`-L' are ignored for objects" 1>&2 ;;
esac
if test -n "$dlfiles$dlprefiles" || test "$dlself" != no; then
$echo "$modename: warning: \`-dlopen' is ignored for objects" 1>&2
......@@ -5688,53 +5714,9 @@ fi\
$echo "$modename: \`$deplib' is not a valid libtool archive" 1>&2
exit $EXIT_FAILURE
fi
if test "X$EGREP" = X ; then
EGREP=egrep
fi
# We do not want portage's install root ($D) present. Check only for
# this if the .la is being installed.
if test "$installed" = yes && test "$D"; then
eval mynewdependency_lib=`echo "$libdir/$name" |sed -e "s:$D:/:g" -e 's:/\+:/:g'`
else
mynewdependency_lib="$libdir/$name"
fi
# Do not add duplicates
if test "$mynewdependency_lib"; then
my_little_ninja_foo_1=`echo $newdependency_libs |$EGREP -e "$mynewdependency_lib"`
if test -z "$my_little_ninja_foo_1"; then
newdependency_libs="$newdependency_libs $mynewdependency_lib"
fi
fi
;;
*)
if test "$installed" = yes; then
# Rather use S=WORKDIR if our version of portage supports it.
# This is because some ebuild (gcc) do not use $S as buildroot.
if test "$PWORKDIR"; then
S="$PWORKDIR"
fi
# We do not want portage's build root ($S) present.
my_little_ninja_foo_2=`echo $deplib |$EGREP -e "$S"`
# We do not want portage's install root ($D) present.
my_little_ninja_foo_3=`echo $deplib |$EGREP -e "$D"`
if test -n "$my_little_ninja_foo_2" && test "$S"; then
mynewdependency_lib=""
elif test -n "$my_little_ninja_foo_3" && test "$D"; then
eval mynewdependency_lib=`echo "$deplib" |sed -e "s:$D:/:g" -e 's:/\+:/:g'`
else
mynewdependency_lib="$deplib"
fi
else
mynewdependency_lib="$deplib"
fi
# Do not add duplicates
if test "$mynewdependency_lib"; then
my_little_ninja_foo_4=`echo $newdependency_libs |$EGREP -e "$mynewdependency_lib"`
if test -z "$my_little_ninja_foo_4"; then
newdependency_libs="$newdependency_libs $mynewdependency_lib"
fi
fi
newdependency_libs="$newdependency_libs $libdir/$name"
;;
*) newdependency_libs="$newdependency_libs $deplib" ;;
esac
done
dependency_libs="$newdependency_libs"
......@@ -5786,10 +5768,6 @@ fi\
case $host,$output,$installed,$module,$dlname in
*cygwin*,*lai,yes,no,*.dll | *mingw*,*lai,yes,no,*.dll) tdlname=../bin/$dlname ;;
esac
# Do not add duplicates
if test "$installed" = yes && test "$D"; then
install_libdir=`echo "$install_libdir" |sed -e "s:$D:/:g" -e 's:/\+:/:g'`
fi
$echo > $output "\
# $outputname - a libtool library file
# Generated by $PROGRAM - GNU $PACKAGE $VERSION$TIMESTAMP
......@@ -6545,7 +6523,7 @@ relink_command=\"$relink_command\""
fi
# Restore saved environment variables
for lt_var in LANG LC_ALL LC_CTYPE LC_COLLATE LC_MESSAGES
for lt_var in LANG LANGUAGE LC_ALL LC_CTYPE LC_COLLATE LC_MESSAGES
do
eval "if test \"\${save_$lt_var+set}\" = set; then
$lt_var=\$save_$lt_var; export $lt_var
......
......@@ -19,30 +19,47 @@ libgraph_tool_LTLIBRARIES = libgraph_tool.la
libgraph_tool_la_includedir = $(pythondir)/graph_tool/include
libgraph_tool_la_SOURCES = \
graph.hh\
graph.cc\
graph_python_interface.cc\
graph_properties.cc\
graph_correlations.cc\
graph_edge_correlations.cc\
graph_correlations_combined.cc\
graph_correlations_neighbours.cc\
graph_assortativity.cc\
graph_clustering.cc\
graph_extended_clustering.cc\
graph_generation.cc\
graph_distance.cc\
graph_distance_sampled.cc\
graph_reciprocity.cc\
graph_minimum_spanning_tree.cc\
graph_community.cc\
graph_community_network.cc\
graph_line_graph.cc\
graph_betweenness.cc\
graph_rewiring.cc\
graph_layout.cc\
graph_io.cc\
graph_bind.cc\
graph_assortativity.cc \
graph_betweenness.cc \
graph_bind.cc \
graph.cc \
graph_clustering.cc \
graph_community.cc \
graph_community_network.cc \
graph_correlations.cc \
graph_correlations_combined.cc \
graph_correlations_combined_corr.cc \
graph_correlations_imp1.cc \
graph_correlations_imp2.cc \
graph_correlations_imp3.cc \
graph_correlations_neighbours.cc \
graph_correlations_neighbours_imp1.cc \
graph_correlations_neighbours_imp2.cc \
graph_correlations_neighbours_imp3.cc \
graph_correlations_neighbours_imp4.cc \
graph_correlations_neighbours_imp5.cc \
graph_correlations_neighbours_imp6.cc \
graph_distance.cc \
graph_distance_sampled.cc \
graph_edge_correlations.cc \
graph_edge_correlations_imp1.cc \
graph_edge_correlations_imp2.cc \
graph_edge_correlations_imp3.cc \
graph_edge_correlations_imp4.cc \
graph_edge_correlations_imp5.cc \
graph_extended_clustering.cc \
graph_filtering.cc \
graph_generation.cc \
graph_io.cc \
graph_layout.cc \
graph_line_graph.cc \
graph_minimum_spanning_tree.cc \
graph_properties.cc \
graph_python_interface.cc \
graph_python_interface_export.cc \
graph_reciprocity.cc \
graph_rewiring.cc \
graph_selectors.cc \
graphml.cpp\
read_graphviz_spirit.cpp\
../boost-workaround/boost/graph/filtered_graph.hpp\
......@@ -50,16 +67,42 @@ libgraph_tool_la_SOURCES = \
../boost-workaround/boost/graph/graphml.hpp
libgraph_tool_la_include_HEADERS = \
graph_adaptor.hh\
graph.hh\
graph_filtering.hh\
graph_python_interface.hh\
graph_selectors.hh\
graph_properties.hh\
shared_map.hh\
histogram.hh\
graph_adaptor.hh \
graph_assortativity.hh \
graph_clustering.hh \
graph_community.hh \
graph_community_network.hh \
graph_correlations_combined.hh \
graph_correlations.hh \
graph_correlations_neighbours.hh \
graph_distance.hh \
graph_distance_sampled.hh \
graph_edge_correlations.hh \
graph_extended_clustering.hh \
graph_filtering.hh \
graph.hh \
graph_properties.hh \
graph_python_interface.hh \
graph_rewiring.hh \
graph_selectors.hh \
graph_util.hh \
histogram.hh \
mpl_nested_loop.hh \
shared_map.hh \
../../config.h
BUILT_SOURCES = \
graph_filtering.hh.gch
## Header precompilation
## FIXME: need a better way to convince libtool to let us do this.
$(libgraph_tool_la_include_HEADERS):
$(BUILT_SOURCES):
$(CXXCOMPILE) $(CXXFLAGS) $(AM_CXXFLAGS) $(AM_CPPFLAGS) -fPIC -DPIC -x c++-header `basename $@ .gch`
mostlyclean-local:
-rm -f *.gch
libgraph_tool_la_LIBADD = \
$(PYTHON_LDFLAGS) \
$(BOOST_LDFLAGS) \
......
......@@ -35,6 +35,7 @@
#include "graph_filtering.hh"
#include "graph_selectors.hh"
#include "graph_properties.hh"
#include "graph_util.hh"
#include "shared_map.hh"
using namespace std;
......@@ -42,24 +43,6 @@ using namespace boost;
using namespace boost::lambda;
using namespace graph_tool;
// this will return the degree type contained in 'degree' or the name of the
// scalar property.
pair<GraphInterface::degree_t,string>
graph_tool::get_degree_type(GraphInterface::deg_t degree)
{
GraphInterface::degree_t deg;
string name;
try
{
deg = boost::get<GraphInterface::degree_t>(degree);
}
catch (bad_get)
{
name = boost::get<std::string>(degree);
deg = GraphInterface::SCALAR;
}
return make_pair(deg,name);
}
// this is the constructor for the graph interface
GraphInterface::GraphInterface()
......@@ -69,13 +52,11 @@ GraphInterface::GraphInterface()
_vertex_index(get(vertex_index,_mg)),
_edge_index(get(edge_index_t(),_mg)),
_vertex_filter_map(_vertex_index),
_vertex_range(make_pair(0.0, numeric_limits<double>::max())),
_vertex_range_include(make_pair(true, true)),
_vertex_range_invert(false),
_vertex_filter_invert(false),
_vertex_filter_active(false),
_edge_filter_map(_edge_index),
_edge_range(make_pair(0.0, numeric_limits<double>::max())),
_edge_range_include(make_pair(true, true)),
_edge_range_invert(false)
_edge_filter_invert(false),
_edge_filter_active(false)
{
}
......@@ -85,313 +66,15 @@ GraphInterface::~GraphInterface()
{
}
// these test whether or not the vertex and filter are active, by checking the
// corresponding property value
bool GraphInterface::IsVertexFilterActive() const
{ return _vertex_filter_property != ""; }
bool GraphInterface::IsEdgeFilterActive() const
{ return _edge_filter_property != ""; }
// this function will get a dynamic property map, and return its static type, if
// it matches a couple of possibilities that have 'double' as value
// type. Otherwise it wraps it around a DynamicPropertyMapWrap
template <class FilterMap, class Descriptor, class IndexMap>
FilterMap choose_filter_map(string property, dynamic_properties& dp)
{
FilterMap filter_map;
try
{
dynamic_property_map& pmap =
find_property_map(dp, property, typeid(Descriptor));
try
{
return get_static_property_map
<vector_property_map<double,IndexMap> >(pmap);
}
catch(bad_cast){}
try
{
return get_static_property_map
<HashedDescriptorMap<IndexMap,double> >(pmap);
}
catch(bad_cast){}
try
{
return get_static_property_map
<vector_property_map<size_t,IndexMap> >(pmap);
}
catch(bad_cast){}
try
{
return get_static_property_map
<HashedDescriptorMap<IndexMap,size_t> >(pmap);
}
catch(bad_cast){}
try
{
return get_static_property_map<IndexMap>(pmap);
}
catch(bad_cast){}
return DynamicPropertyMapWrap<double,Descriptor>(pmap);
}
catch (property_not_found)
{
throw GraphException("property " + property + " not found");
}
}
// this will set enable the vertex filter, according to the given property. If
// property is an empty string, this will disable the filter
void GraphInterface::SetVertexFilterProperty(string property)
{
#ifndef NO_RANGE_FILTERING
_vertex_filter_property = property;
if (property != "")
{
_vertex_filter_map = choose_filter_map<vertex_filter_map_t,
vertex_t,vertex_index_map_t>
(property, _properties);
}
else
{
_vertex_filter_map = _vertex_index;
_vertex_range = make_pair(0.0, numeric_limits<double>::max());
_vertex_range_include = make_pair(true, true);
_vertex_range_invert = false;
}
#else
if (property != "")
throw GraphException("support for graph range filtering was not enabled"
" during compilation.");
#endif
}
// this will set enable the edge filter, according to the given property. If
// property is an empty string, this will disable the filter
void GraphInterface::SetEdgeFilterProperty(string property)
{
#ifndef NO_RANGE_FILTERING
_edge_filter_property = property;
if (property != "")
{
_edge_filter_map = choose_filter_map<edge_filter_map_t,
edge_t,edge_index_map_t>
(property, _properties);
}
else
{
_edge_filter_map = _edge_index;
_edge_range = make_pair(0.0, numeric_limits<double>::max());
_edge_range_include = make_pair(true, true);
_edge_range_invert = false;
}
#else
if (property != "")
throw GraphException("support for graph range filtering was not enabled"
" during compilation.");
#endif
}
void GraphInterface::SetVertexFilterRange(pair<double,double> allowed_range,
pair<bool,bool> include, bool invert)
{
#ifndef NO_RANGE_FILTERING
if (isinf(allowed_range.first) == 1)
allowed_range.first = numeric_limits<double>::max();
else if (isinf(allowed_range.first) == -1)
allowed_range.first = -numeric_limits<double>::max();
if (isinf(allowed_range.second) == 1)
allowed_range.second = numeric_limits<double>::max();
else if (isinf(allowed_range.second) == -1)
allowed_range.second = -numeric_limits<double>::max();
_vertex_range = allowed_range;
_vertex_range_include = include;
_vertex_range_invert = invert;
#else
throw GraphException("support for graph range filtering was not enabled"
" during compilation.");
#endif
}
void GraphInterface::SetEdgeFilterRange(pair<double,double> allowed_range,
pair<bool,bool> include, bool invert)
{
#ifndef NO_RANGE_FILTERING
if (isinf(allowed_range.first) == 1)
allowed_range.first = numeric_limits<double>::max();
else if (isinf(allowed_range.first) == -1)
allowed_range.first = -numeric_limits<double>::max();
if (isinf(allowed_range.second) == 1)
allowed_range.second = numeric_limits<double>::max();
else if (isinf(allowed_range.second) == -1)
allowed_range.second = -numeric_limits<double>::max();
_edge_range = allowed_range;
_edge_range_include = include;
_edge_range_invert = invert;
#else
throw GraphException("support for graph range filtering was not enabled"
" during compilation.");
#endif
}
// this function will reindex all the edges, in the order in which they are
// found, taking care of preserving all the properties
void GraphInterface::ReIndexEdges()
{
size_t n_edges = num_edges(_mg);
if (n_edges == 0)
return;