Commit 1fd8cd95 authored by Tiago Peixoto's avatar Tiago Peixoto
Browse files

Fix sfdp_layout() implementation for unconnected graphs

parent 11e2b682
......@@ -49,15 +49,23 @@ void sfdp_layout(GraphInterface& g, boost::any pos, boost::any vweight,
typedef mpl::push_back<edge_scalar_properties, eweight_map_t>::type
edge_props_t;
typedef typename property_map_type::apply<int32_t,
GraphInterface::vertex_index_map_t>::type
group_map_t;
double C = python::extract<double>(spring_parms[0]);
double K = python::extract<double>(spring_parms[1]);
double p = python::extract<double>(spring_parms[2]);
double gamma = python::extract<double>(spring_parms[3]);
group_map_t groups =
any_cast<group_map_t>(python::extract<any>(spring_parms[4]));
if(vweight.empty())
vweight = vweight_map_t(1);
if(eweight.empty())
eweight = eweight_map_t(1);
typedef ConstantPropertyMap<bool,GraphInterface::vertex_t> pin_map_t;
typedef mpl::vector<typename property_map_type::apply
<uint8_t,
......@@ -69,10 +77,10 @@ void sfdp_layout(GraphInterface& g, boost::any pos, boost::any vweight,
pin = pin_map_t(false);
run_action<graph_tool::detail::never_directed>()
(g,
bind<void>(get_sfdp_layout(C, K, p, theta, init_step,
bind<void>(get_sfdp_layout(C, K, p, theta, gamma, init_step,
step_schedule, max_level, epsilon,
max_iter, adaptive),
_1, g.GetVertexIndex(), _2, _3, _4, _5, verbose),
_1, g.GetVertexIndex(), _2, _3, _4, _5, groups, verbose),
vertex_floating_vector_properties(), vertex_props_t(), edge_props_t(),
pin_props_t())(pos, vweight, eweight, pin);
}
......
......@@ -100,8 +100,8 @@ public:
double get_w()
{
return max(_ur[0] - _ll[0],
_ur[1] - _ll[1]);
return sqrt(pow(_ur[0] - _ll[0], 2) +
pow(_ur[1] - _ll[1], 2));
}
Weight get_count()
......@@ -176,22 +176,22 @@ inline double norm(Pos& x)
struct get_sfdp_layout
{
get_sfdp_layout(double C, double K, double p, double theta, double init_step,
double step_schedule, size_t max_level, double epsilon,
size_t max_iter, bool simple)
: C(C), K(K), p(p), theta(theta), init_step(init_step),
get_sfdp_layout(double C, double K, double p, double theta, double gamma,
double init_step, double step_schedule, size_t max_level,
double epsilon, size_t max_iter, bool simple)
: C(C), K(K), p(p), theta(theta), gamma(gamma), init_step(init_step),
step_schedule(step_schedule), epsilon(epsilon),
max_level(max_level), max_iter(max_iter), simple(simple) {}
double C, K, p, theta, init_step, step_schedule, epsilon;
double C, K, p, theta, gamma, init_step, step_schedule, epsilon;
size_t max_level, max_iter;
bool simple;
template <class Graph, class VertexIndex, class PosMap, class VertexWeightMap,
class EdgeWeightMap, class PinMap>
class EdgeWeightMap, class PinMap, class GroupMap>
void operator()(Graph& g, VertexIndex vertex_index, PosMap pos,
VertexWeightMap vweight, EdgeWeightMap eweight, PinMap pin,
bool verbose) const
GroupMap group, bool verbose) const
{
typedef typename property_traits<PosMap>::value_type pos_t;
typedef typename property_traits<PosMap>::value_type::value_type val_t;
......@@ -201,22 +201,45 @@ struct get_sfdp_layout
pos_t ll(2, numeric_limits<val_t>::max()),
ur(2, -numeric_limits<val_t>::max());
int i, N = num_vertices(g);
#pragma omp parallel for default(shared) private(i)
vector<pos_t> group_cm, group_cm_tmp;
vector<vweight_t> group_size;
int i, N = num_vertices(g), HN=0;
for (i = 0; i < N; ++i)
{
typename graph_traits<Graph>::vertex_descriptor v =
vertex(i, g);
if (v == graph_traits<Graph>::null_vertex())
continue;
pos[v].resize(2);
pos[v].resize(2, 0);
size_t s = group[v];
{
if (s >= group_cm.size())
{
group_cm.resize(s + 1);
group_cm_tmp.resize(s + 1);
group_size.resize(s + 1, 0);
}
group_cm[s].resize(2, 0);
group_cm_tmp[s].resize(2, 0);
group_size[s] += get(vweight, v);
}
for (size_t j = 0; j < 2; ++j)
{
#pragma omp critical
ll[j] = min(pos[v][j], ll[j]);
ur[j] = max(pos[v][j], ur[j]);
group_cm[s][j] += pos[v][j] * get(vweight, v);
}
HN++;
}
for (size_t s = 0; s < group_size.size(); ++s)
{
group_cm[s].resize(2, 0);
for (size_t j = 0; j < 2; ++j)
group_cm[s][j] /= group_size[s];
}
val_t delta = epsilon * K + 1, E = 0, E0;
......@@ -261,9 +284,19 @@ struct get_sfdp_layout
continue;
if (pin[v])
{
#pragma omp critical
group_cm_tmp[group[v]].resize(2, 0);
for (size_t l = 0; l < 2; ++l)
{
group_cm_tmp[group[v]][l] += pos[v][l] * get(vweight, v);
}
continue;
}
ftot[0] = ftot[1] = 0;
// global repulsive forces
Q.clear();
Q.push_back(ref(qt));
while (!Q.empty())
......@@ -292,7 +325,7 @@ struct get_sfdp_layout
double w = q.get_w();
q.get_cm(cm);
double d = get_diff(cm, pos[v], diff);
if (w / d > theta)
if (w > theta * d)
{
for(size_t j = 0; j < 4; ++j)
{
......@@ -314,6 +347,7 @@ struct get_sfdp_layout
}
}
// local attractive forces
typename graph_traits<Graph>::out_edge_iterator e, e_end;
for (tie(e,e_end) = out_edges(v, g); e != e_end; ++e)
{
......@@ -333,10 +367,27 @@ struct get_sfdp_layout
ftot[l] += f * diff[l];
}
// inter-group attractive forces
for (size_t s = 0; s < group_cm.size(); ++s)
{
if (s == size_t(group[v]))
continue;
val_t d = get_diff(group_cm[s], pos[v], diff);
if (d == 0)
continue;
double Kp = K * pow(HN, 2);
val_t f = f_a(Kp, group_cm[s], pos[v]) * gamma * \
group_size[s] * get(vweight, v);
for (size_t l = 0; l < 2; ++l)
ftot[l] += f * diff[l];
}
E += pow(norm(ftot), 2);
{
#pragma omp critical
group_cm_tmp[group[v]].resize(2, 0);
for (size_t l = 0; l < 2; ++l)
{
ftot[l] *= step;
......@@ -344,6 +395,8 @@ struct get_sfdp_layout
nll[l] = min(pos[v][l], ll[l]);
nur[l] = max(pos[v][l], ur[l]);
group_cm_tmp[group[v]][l] += pos[v][l] * get(vweight, v);
}
}
delta += norm(ftot);
......@@ -354,6 +407,17 @@ struct get_sfdp_layout
ur = nur;
delta /= nmoves;
for (size_t s = 0; s < group_size.size(); ++s)
{
for (size_t j = 0; j < 2; ++j)
{
group_cm_tmp[s][j] /= group_size[s];
group_cm[s][j] = 0;
}
}
group_cm.swap(group_cm_tmp);
if (verbose)
cout << n_iter << " " << E << " " << step << " "
<< delta << " " << max_level << endl;
......
......@@ -366,16 +366,17 @@ def _avg_edge_distance(g, pos):
def coarse_graphs(g, method="hybrid", mivs_thres=0.9, ec_thres=0.75,
weighted_coarse=False, verbose=False):
weighted_coarse=False, eweight=None, vweight=None,
verbose=False):
cg = [[g, None, None, None, None, None]]
if weighted_coarse:
cg[-1][2], cg[-1][3] = vweight, eweight
mivs = not (method in ["hybrid", "ec"])
while True:
u = _coarse_graph(cg[-1][0], cg[-1][2], cg[-1][3], mivs)
if (mivs and
u[0].num_vertices() > mivs_thres * cg[-1][0].num_vertices()):
break
if u[0].num_vertices() > ec_thres * cg[-1][0].num_vertices():
if method == "hybrid":
thres = mivs_thres if mivs else ec_thres
if u[0].num_vertices() >= thres * cg[-1][0].num_vertices():
if method == "hybrid" and not mivs:
mivs = True
else:
break
......@@ -422,26 +423,66 @@ def coarse_graphs(g, method="hybrid", mivs_thres=0.9, ec_thres=0.75,
def sfdp_layout(g, vweight=None, eweight=None, pin=None, C=0.2, K=None, p=2.,
theta=0.6, init_step=None, cooling_step=0.9,
adaptive_cooling=True, max_level=11, epsilon=1e-1, max_iter=0,
pos=None, multilevel=None, coarse_method="hybrid",
mivs_thres=0.9, ec_thres=0.75,
weighted_coarse=False, verbose=False):
r"""Calculate the sfdp spring-block layout of the graph.
theta=0.6, max_level=11, gamma=1., init_step=None,
cooling_step=0.9, adaptive_cooling=True, epsilon=1e-1,
max_iter=0, pos=None, multilevel=None, coarse_method="hybrid",
mivs_thres=0.9, ec_thres=0.75, weighted_coarse=False,
verbose=False):
r"""Obtain the SFDP spring-block layout of the graph.
Parameters
----------
g : :class:`~graph_tool.Graph`
Graph to be used.
weight : :class:`~graph_tool.PropertyMap` (optional, default: ``None``)
vweight : :class:`~graph_tool.PropertyMap` (optional, default: ``None``)
A vertex property map with the respective weights.
eweight : :class:`~graph_tool.PropertyMap` (optional, default: ``None``)
An edge property map with the respective weights.
epsilon : float (optional, default: ``1e-6``)
Convergence criterion.
max_iter : int (optional, default: ``1000``)
pin : :class:`~graph_tool.PropertyMap` (optional, default: ``None``)
A vertex property map with with boolean values, which, if given,
specifies the vertices which will not have their positions modified.
C : float (optional, default: ``0.2``)
Relative strength of repulsive forces.
K : float (optional, default: ``None``)
Optimal edge length. If not provided, it will be taken to be the average
edge distance in the initial layout.
p : float (optional, default: ``2``)
Repulsive force exponent.
theta : float (optional, default: ``0.6``)
Quadtree opening parameter, a.k.a. Barnes–Hut opening criterion.
max_level : int (optional, default: ``11``)
Maximum quadtree level.
gamma : float (optional, default: ``1.0``)
Strength of the attractive force between connected components.
init_step : float (optional, default: ``None``)
Initial update step. If not provided, it will be chosen automatically.
cooling_step : float (optional, default: ``0.9``)
Cooling update step.
adaptive_cooling : bool (optional, default: ``True``)
Use an adaptive cooling scheme.
epsilon : float (optional, default: ``0.1``)
Relative convergence criterion.
max_iter : int (optional, default: ``0``)
Maximum number of iterations. If this value is ``0``, it runs until
convergence.
pos : :class:`~graph_tool.PropertyMap` (optional, default: ``None``)
Vector vertex property maps where the coordinates should be stored.
Initial vertex layout. If not provided, it will be randomly chosen.
multilevel : bool (optional, default: ``None``)
Use a multilevel layout algorithm. If ``None`` is given, it will be
activated based on the size of the graph.
coarse_method : str (optional, default: ``"hybrid"``)
Coarsening method used if ``multilevel == True``. Allowed methods are
``"hybrid"``, ``"mivs"`` and ``"ec"``.
mivs_thres : float (optional, default: ``0.9``)
If the relative size of the MIVS coarse graph is above this value, the
coarsening stops.
ec_thres : float (optional, default: ``0.75``)
If the relative size of the EC coarse graph is above this value, the
coarsening stops.
weighted_coarse : bool (optional, default: ``False``)
Use weighted coarse graphs.
verbose : bool (optional, default: ``False``)
Provide verbose information.
Returns
-------
......@@ -451,30 +492,28 @@ def sfdp_layout(g, vweight=None, eweight=None, pin=None, C=0.2, K=None, p=2.,
Notes
-----
This algorithm is defined in [geipel-self-organization-2007]_, and has
complexity :math:`O(V^2)`.
This algorithm is defined in [hu-multilevel-2005]_, and has
complexity :math:`O(V\log V)`.
Examples
--------
>>> from numpy.random import seed, zipf
>>> seed(42)
>>> g = gt.price_network(300)
>>> pos = gt.arf_layout(g, max_iter=0)
>>> gt.graph_draw(g, pos=pos, pin=True, output="graph-draw-arf.pdf")
>>> g = gt.price_network(3000)
>>> pos = gt.sfdp_layout(g)
>>> gt.graph_draw(g, pos=pos, output="graph-draw-sfdp.pdf")
<...>
.. figure:: graph-draw-arf.*
.. figure:: graph-draw-sfdp.*
:align: center
ARF layout of a Price network.
SFDP layout of a Price network.
References
----------
.. [geipel-self-organization-2007] Markus M. Geipel, "Self-Organization
applied to Dynamic Network Layout", International Journal of Modern
Physics C vol. 18, no. 10 (2007), pp. 1537-1549,
:doi:`10.1142/S0129183107011558`, :arxiv:`0704.1748v5`
.. _arf: http://www.sg.ethz.ch/research/graphlayout
.. [hu-multilevel-2005] Yifan Hu, "Efficient and High Quality Force-Directed
Graph", Mathematica Journal, vol. 10, Issue 1, pp. 37-71, (2005)
http://www.mathematica-journal.com/issue/v10i1/graph_draw.html
"""
if pos is None:
......@@ -496,10 +535,14 @@ def sfdp_layout(g, vweight=None, eweight=None, pin=None, C=0.2, K=None, p=2.,
multilevel = g.num_vertices() > 1000
if multilevel:
if eweight is not None or vweight is not None:
weighted_coarse = True
cgs = coarse_graphs(g, method=coarse_method,
mivs_thres=mivs_thres,
ec_thres=ec_thres,
weighted_coarse=weighted_coarse,
eweight=eweight,
vweight=vweight,
verbose=verbose)
count = 0
for u, pos, K, vcount, ecount in cgs:
......@@ -512,7 +555,7 @@ def sfdp_layout(g, vweight=None, eweight=None, pin=None, C=0.2, K=None, p=2.,
vweight=vcount if weighted_coarse else None,
eweight=ecount if weighted_coarse else None,
C=C, K=K, p=p,
theta=theta, epsilon=epsilon,
theta=theta, gamma=gamma, epsilon=epsilon,
max_iter=max_iter,
cooling_step=cooling_step,
adaptive_cooling=False,
......@@ -532,11 +575,13 @@ def sfdp_layout(g, vweight=None, eweight=None, pin=None, C=0.2, K=None, p=2.,
return pos
if g.num_vertices() <= 50:
max_level = 0
groups = label_components(g)[0]
libgraph_tool_layout.sfdp_layout(g._Graph__graph, _prop("v", g, pos),
_prop("v", g, vweight),
_prop("e", g, eweight),
_prop("v", g, pin), (C, K, p), theta,
init_step, cooling_step, max_level,
_prop("v", g, pin),
(C, K, p, gamma, _prop("v", g, groups)),
theta, init_step, cooling_step, max_level,
epsilon, max_iter, not adaptive_cooling,
verbose)
return pos
......
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