/* -*- mode: C -*- */
/* vim:set ts=4 sw=4 sts=4 et: */
/*
IGraph R package.
Copyright (C) 2003-2014 Gabor Csardi <csardi.gabor@gmail.com>
334 Harvard street, Cambridge, MA 02139 USA
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
*/
#include "igraph_layout.h"
#include "igraph_random.h"
#include "igraph_memory.h"
#include "igraph_iterators.h"
#include "igraph_interface.h"
#include "igraph_adjlist.h"
#include "igraph_progress.h"
#include "igraph_interrupt_internal.h"
#include "igraph_paths.h"
#include "igraph_structural.h"
#include "igraph_visitor.h"
#include "igraph_topology.h"
#include "igraph_components.h"
#include "igraph_types_internal.h"
#include "igraph_dqueue.h"
#include "igraph_arpack.h"
#include "igraph_blas.h"
#include "igraph_centrality.h"
#include "igraph_eigen.h"
#include "config.h"
#include <math.h>
#include "igraph_math.h"
/**
* \section about_layouts
*
* <para>Layout generator functions (or at least most of them) try to place the
* vertices and edges of a graph on a 2D plane or in 3D space in a way
* which visually pleases the human eye.</para>
*
* <para>They take a graph object and a number of parameters as arguments
* and return an \type igraph_matrix_t, in which each row gives the
* coordinates of a vertex.</para>
*/
/**
* \ingroup layout
* \function igraph_layout_random
* \brief Places the vertices uniform randomly on a plane.
*
* \param graph Pointer to an initialized graph object.
* \param res Pointer to an initialized matrix object. This will
* contain the result and will be resized as needed.
* \return Error code. The current implementation always returns with
* success.
*
* Time complexity: O(|V|), the
* number of vertices.
*/
int igraph_layout_random(const igraph_t *graph, igraph_matrix_t *res) {
long int no_of_nodes = igraph_vcount(graph);
long int i;
IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, 2));
RNG_BEGIN();
for (i = 0; i < no_of_nodes; i++) {
MATRIX(*res, i, 0) = RNG_UNIF(-1, 1);
MATRIX(*res, i, 1) = RNG_UNIF(-1, 1);
}
RNG_END();
return 0;
}
/**
* \function igraph_layout_random_3d
* \brief Random layout in 3D
*
* \param graph The graph to place.
* \param res Pointer to an initialized matrix object. It will be
* resized to hold the result.
* \return Error code. The current implementation always returns with
* success.
*
* Added in version 0.2.</para><para>
*
* Time complexity: O(|V|), the number of vertices.
*/
int igraph_layout_random_3d(const igraph_t *graph, igraph_matrix_t *res) {
long int no_of_nodes = igraph_vcount(graph);
long int i;
IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, 3));
RNG_BEGIN();
for (i = 0; i < no_of_nodes; i++) {
MATRIX(*res, i, 0) = RNG_UNIF(-1, 1);
MATRIX(*res, i, 1) = RNG_UNIF(-1, 1);
MATRIX(*res, i, 2) = RNG_UNIF(-1, 1);
}
RNG_END();
return 0;
}
/**
* \ingroup layout
* \function igraph_layout_circle
* \brief Places the vertices uniformly on a circle, in the order of vertex ids.
*
* \param graph Pointer to an initialized graph object.
* \param res Pointer to an initialized matrix object. This will
* contain the result and will be resized as needed.
* \param order The order of the vertices on the circle. The vertices
* not included here, will be placed at (0,0). Supply
* \ref igraph_vss_all() here for all vertices, in the order of
* their vertex ids.
* \return Error code.
*
* Time complexity: O(|V|), the
* number of vertices.
*/
int igraph_layout_circle(const igraph_t *graph, igraph_matrix_t *res,
igraph_vs_t order) {
long int no_of_nodes = igraph_vcount(graph);
igraph_integer_t vs_size;
long int i;
igraph_vit_t vit;
IGRAPH_CHECK(igraph_vs_size(graph, &order, &vs_size));
IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, 2));
igraph_matrix_null(res);
igraph_vit_create(graph, order, &vit);
for (i = 0; !IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) {
igraph_real_t phi = 2 * M_PI / vs_size * i;
int idx = IGRAPH_VIT_GET(vit);
MATRIX(*res, idx, 0) = cos(phi);
MATRIX(*res, idx, 1) = sin(phi);
}
igraph_vit_destroy(&vit);
return 0;
}
/**
* \function igraph_layout_star
* Generate a star-like layout
*
* \param graph The input graph.
* \param res Pointer to an initialized matrix object. This will
* contain the result and will be resized as needed.
* \param center The id of the vertex to put in the center.
* \param order A numeric vector giving the order of the vertices
* (including the center vertex!). If a null pointer, then the
* vertices are placed in increasing vertex id order.
* \return Error code.
*
* Time complexity: O(|V|), linear in the number of vertices.
*
* \sa \ref igraph_layout_circle() and other layout generators.
*/
int igraph_layout_star(const igraph_t *graph, igraph_matrix_t *res,
igraph_integer_t center, const igraph_vector_t *order) {
long int no_of_nodes = igraph_vcount(graph);
long int c = center;
long int i;
igraph_real_t step;
igraph_real_t phi;
if (order && igraph_vector_size(order) != no_of_nodes) {
IGRAPH_ERROR("Invalid order vector length", IGRAPH_EINVAL);
}
IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, 2));
if (no_of_nodes == 1) {
MATRIX(*res, 0, 0) = MATRIX(*res, 0, 1) = 0.0;
} else {
for (i = 0, step = 2 * M_PI / (no_of_nodes - 1), phi = 0;
i < no_of_nodes; i++) {
long int node = order ? (long int) VECTOR(*order)[i] : i;
if (node != c) {
MATRIX(*res, node, 0) = cos(phi);
MATRIX(*res, node, 1) = sin(phi);
phi += step;
} else {
MATRIX(*res, node, 0) = MATRIX(*res, node, 1) = 0.0;
}
}
}
return 0;
}
/**
* \function igraph_layout_sphere
* \brief Places vertices (more or less) uniformly on a sphere.
*
* </para><para>
* The algorithm was described in the following paper:
* Distributing many points on a sphere by E.B. Saff and
* A.B.J. Kuijlaars, \emb Mathematical Intelligencer \eme 19.1 (1997)
* 5--11.
*
* \param graph Pointer to an initialized graph object.
* \param res Pointer to an initialized matrix object. This will
* contain the result and will be resized as needed.
* \return Error code. The current implementation always returns with
* success.
*
* Added in version 0.2.</para><para>
*
* Time complexity: O(|V|), the number of vertices in the graph.
*/
int igraph_layout_sphere(const igraph_t *graph, igraph_matrix_t *res) {
long int no_of_nodes = igraph_vcount(graph);
long int i;
igraph_real_t h;
IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, 3));
if (no_of_nodes != 0) {
MATRIX(*res, 0, 0) = M_PI;
MATRIX(*res, 0, 1) = 0;
}
for (i = 1; i < no_of_nodes - 1; i++) {
h = -1 + 2 * i / (double)(no_of_nodes - 1);
MATRIX(*res, i, 0) = acos(h);
MATRIX(*res, i, 1) = fmod((MATRIX(*res, i - 1, 1) +
3.6 / sqrt(no_of_nodes * (1 - h * h))), 2 * M_PI);
IGRAPH_ALLOW_INTERRUPTION();
}
if (no_of_nodes >= 2) {
MATRIX(*res, no_of_nodes - 1, 0) = 0;
MATRIX(*res, no_of_nodes - 1, 1) = 0;
}
for (i = 0; i < no_of_nodes; i++) {
igraph_real_t x = cos(MATRIX(*res, i, 1)) * sin(MATRIX(*res, i, 0));
igraph_real_t y = sin(MATRIX(*res, i, 1)) * sin(MATRIX(*res, i, 0));
igraph_real_t z = cos(MATRIX(*res, i, 0));
MATRIX(*res, i, 0) = x;
MATRIX(*res, i, 1) = y;
MATRIX(*res, i, 2) = z;
IGRAPH_ALLOW_INTERRUPTION();
}
return 0;
}
/**
* \ingroup layout
* \function igraph_layout_grid
* \brief Places the vertices on a regular grid on the plane.
*
* \param graph Pointer to an initialized graph object.
* \param res Pointer to an initialized matrix object. This will
* contain the result and will be resized as needed.
* \param width The number of vertices in a single row of the grid.
* When zero or negative, the width of the grid will be the
* square root of the number of vertices, rounded up if needed.
* \return Error code. The current implementation always returns with
* success.
*
* Time complexity: O(|V|), the number of vertices.
*/
int igraph_layout_grid(const igraph_t *graph, igraph_matrix_t *res, long int width) {
long int i, no_of_nodes = igraph_vcount(graph);
igraph_real_t x, y;
IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, 2));
if (width <= 0) {
width = (long int) ceil(sqrt(no_of_nodes));
}
x = y = 0;
for (i = 0; i < no_of_nodes; i++) {
MATRIX(*res, i, 0) = x++;
MATRIX(*res, i, 1) = y;
if (x == width) {
x = 0; y++;
}
}
return 0;
}
/**
* \ingroup layout
* \function igraph_layout_grid_3d
* \brief Places the vertices on a regular grid in the 3D space.
*
* \param graph Pointer to an initialized graph object.
* \param res Pointer to an initialized matrix object. This will
* contain the result and will be resized as needed.
* \param width The number of vertices in a single row of the grid. When
* zero or negative, the width is determined automatically.
* \param height The number of vertices in a single column of the grid. When
* zero or negative, the height is determined automatically.
*
* \return Error code. The current implementation always returns with
* success.
*
* Time complexity: O(|V|), the number of vertices.
*/
int igraph_layout_grid_3d(const igraph_t *graph, igraph_matrix_t *res,
long int width, long int height) {
long int i, no_of_nodes = igraph_vcount(graph);
igraph_real_t x, y, z;
IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, 3));
if (width <= 0 && height <= 0) {
width = height = (long int) ceil(pow(no_of_nodes, 1.0 / 3));
} else if (width <= 0) {
width = (long int) ceil(sqrt(no_of_nodes / (double)height));
} else if (height <= 0) {
height = (long int) ceil(sqrt(no_of_nodes / (double)width));
}
x = y = z = 0;
for (i = 0; i < no_of_nodes; i++) {
MATRIX(*res, i, 0) = x++;
MATRIX(*res, i, 1) = y;
MATRIX(*res, i, 2) = z;
if (x == width) {
x = 0; y++;
if (y == height) {
y = 0; z++;
}
}
}
return 0;
}
int igraph_layout_springs(const igraph_t *graph, igraph_matrix_t *res,
igraph_real_t mass, igraph_real_t equil, igraph_real_t k,
igraph_real_t repeqdis, igraph_real_t kfr, igraph_bool_t repulse) {
IGRAPH_UNUSED(graph); IGRAPH_UNUSED(res); IGRAPH_UNUSED(mass);
IGRAPH_UNUSED(equil); IGRAPH_UNUSED(k); IGRAPH_UNUSED(repeqdis);
IGRAPH_UNUSED(kfr); IGRAPH_UNUSED(repulse);
IGRAPH_ERROR("Springs layout not implemented", IGRAPH_UNIMPLEMENTED);
/* TODO */
return 0;
}
static void igraph_i_norm2d(igraph_real_t *x, igraph_real_t *y) {
igraph_real_t len = sqrt((*x) * (*x) + (*y) * (*y));
if (len != 0) {
*x /= len;
*y /= len;
}
}
/**
* \function igraph_layout_lgl
* \brief Force based layout algorithm for large graphs.
*
* </para><para>
* This is a layout generator similar to the Large Graph Layout
* algorithm and program
* (http://lgl.sourceforge.net/). But unlike LGL, this
* version uses a Fruchterman-Reingold style simulated annealing
* algorithm for placing the vertices. The speedup is achieved by
* placing the vertices on a grid and calculating the repulsion only
* for vertices which are closer to each other than a limit.
*
* \param graph The (initialized) graph object to place.
* \param res Pointer to an initialized matrix object to hold the
* result. It will be resized if needed.
* \param maxit The maximum number of cooling iterations to perform
* for each layout step. A reasonable default is 150.
* \param maxdelta The maximum length of the move allowed for a vertex
* in a single iteration. A reasonable default is the number of
* vertices.
* \param area This parameter gives the area of the square on which
* the vertices will be placed. A reasonable default value is the
* number of vertices squared.
* \param coolexp The cooling exponent. A reasonable default value is
* 1.5.
* \param repulserad Determines the radius at which vertex-vertex
* repulsion cancels out attraction of adjacent vertices. A
* reasonable default value is \p area times the number of vertices.
* \param cellsize The size of the grid cells, one side of the
* square. A reasonable default value is the fourth root of
* \p area (or the square root of the number of vertices if \p area
* is also left at its default value).
* \param proot The root vertex, this is placed first, its neighbors
* in the first iteration, second neighbors in the second, etc. If
* negative then a random vertex is chosen.
* \return Error code.
*
* Added in version 0.2.</para><para>
*
* Time complexity: ideally O(dia*maxit*(|V|+|E|)), |V| is the number
* of vertices,
* dia is the diameter of the graph, worst case complexity is still
* O(dia*maxit*(|V|^2+|E|)), this is the case when all vertices happen to be
* in the same grid cell.
*/
int igraph_layout_lgl(const igraph_t *graph, igraph_matrix_t *res,
igraph_integer_t maxit, igraph_real_t maxdelta,
igraph_real_t area, igraph_real_t coolexp,
igraph_real_t repulserad, igraph_real_t cellsize,
igraph_integer_t proot) {
long int no_of_nodes = igraph_vcount(graph);
long int no_of_edges = igraph_ecount(graph);
igraph_t mst;
long int root;
long int no_of_layers, actlayer = 0;
igraph_vector_t vids;
igraph_vector_t layers;
igraph_vector_t parents;
igraph_vector_t edges;
igraph_2dgrid_t grid;
igraph_vector_t eids;
igraph_vector_t forcex;
igraph_vector_t forcey;
igraph_real_t frk = sqrt(area / no_of_nodes);
igraph_real_t H_n = 0;
IGRAPH_CHECK(igraph_minimum_spanning_tree_unweighted(graph, &mst));
IGRAPH_FINALLY(igraph_destroy, &mst);
IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, 2));
/* Determine the root vertex, random pick right now */
if (proot < 0) {
root = RNG_INTEGER(0, no_of_nodes - 1);
} else {
root = proot;
}
/* Assign the layers */
IGRAPH_VECTOR_INIT_FINALLY(&vids, 0);
IGRAPH_VECTOR_INIT_FINALLY(&layers, 0);
IGRAPH_VECTOR_INIT_FINALLY(&parents, 0);
IGRAPH_CHECK(igraph_i_bfs(&mst, (igraph_integer_t) root, IGRAPH_ALL, &vids,
&layers, &parents));
no_of_layers = igraph_vector_size(&layers) - 1;
/* We don't need the mst any more */
igraph_destroy(&mst);
igraph_empty(&mst, 0, IGRAPH_UNDIRECTED); /* to make finalization work */
IGRAPH_VECTOR_INIT_FINALLY(&edges, 0);
IGRAPH_CHECK(igraph_vector_reserve(&edges, no_of_edges));
IGRAPH_VECTOR_INIT_FINALLY(&eids, 0);
IGRAPH_VECTOR_INIT_FINALLY(&forcex, no_of_nodes);
IGRAPH_VECTOR_INIT_FINALLY(&forcey, no_of_nodes);
/* Place the vertices randomly */
IGRAPH_CHECK(igraph_layout_random(graph, res));
igraph_matrix_scale(res, 1e6);
/* This is the grid for calculating the vertices near to a given vertex */
IGRAPH_CHECK(igraph_2dgrid_init(&grid, res,
-sqrt(area / M_PI), sqrt(area / M_PI), cellsize,
-sqrt(area / M_PI), sqrt(area / M_PI), cellsize));
IGRAPH_FINALLY(igraph_2dgrid_destroy, &grid);
/* Place the root vertex */
igraph_2dgrid_add(&grid, root, 0, 0);
for (actlayer = 1; actlayer < no_of_layers; actlayer++) {
H_n += 1.0 / actlayer;
}
for (actlayer = 1; actlayer < no_of_layers; actlayer++) {
igraph_real_t c = 1;
long int i, j;
igraph_real_t massx, massy;
igraph_real_t px, py;
igraph_real_t sx, sy;
long int it = 0;
igraph_real_t epsilon = 10e-6;
igraph_real_t maxchange = epsilon + 1;
long int pairs;
igraph_real_t sconst = sqrt(area / M_PI) / H_n;
igraph_2dgrid_iterator_t vidit;
/* printf("Layer %li:\n", actlayer); */
/*-----------------------------------------*/
/* Step 1: place the next layer on spheres */
/*-----------------------------------------*/
RNG_BEGIN();
j = (long int) VECTOR(layers)[actlayer];
for (i = (long int) VECTOR(layers)[actlayer - 1];
i < VECTOR(layers)[actlayer]; i++) {
long int vid = (long int) VECTOR(vids)[i];
long int par = (long int) VECTOR(parents)[vid];
IGRAPH_ALLOW_INTERRUPTION();
igraph_2dgrid_getcenter(&grid, &massx, &massy);
igraph_i_norm2d(&massx, &massy);
px = MATRIX(*res, vid, 0) - MATRIX(*res, par, 0);
py = MATRIX(*res, vid, 1) - MATRIX(*res, par, 1);
igraph_i_norm2d(&px, &py);
sx = c * (massx + px) + MATRIX(*res, vid, 0);
sy = c * (massy + py) + MATRIX(*res, vid, 1);
/* The neighbors of 'vid' */
while (j < VECTOR(layers)[actlayer + 1] &&
VECTOR(parents)[(long int)VECTOR(vids)[j]] == vid) {
igraph_real_t rx, ry;
if (actlayer == 1) {
igraph_real_t phi = 2 * M_PI / (VECTOR(layers)[2] - 1) * (j - 1);
rx = cos(phi);
ry = sin(phi);
} else {
rx = RNG_UNIF(-1, 1);
ry = RNG_UNIF(-1, 1);
}
igraph_i_norm2d(&rx, &ry);
rx = rx / actlayer * sconst;
ry = ry / actlayer * sconst;
igraph_2dgrid_add(&grid, (long int) VECTOR(vids)[j], sx + rx, sy + ry);
j++;
}
}
RNG_END();
/*-----------------------------------------*/
/* Step 2: add the edges of the next layer */
/*-----------------------------------------*/
for (j = (long int) VECTOR(layers)[actlayer];
j < VECTOR(layers)[actlayer + 1]; j++) {
long int vid = (long int) VECTOR(vids)[j];
long int k;
IGRAPH_ALLOW_INTERRUPTION();
IGRAPH_CHECK(igraph_incident(graph, &eids, (igraph_integer_t) vid,
IGRAPH_ALL));
for (k = 0; k < igraph_vector_size(&eids); k++) {
long int eid = (long int) VECTOR(eids)[k];
igraph_integer_t from, to;
igraph_edge(graph, (igraph_integer_t) eid, &from, &to);
if ((from != vid && igraph_2dgrid_in(&grid, from)) ||
(to != vid && igraph_2dgrid_in(&grid, to))) {
igraph_vector_push_back(&edges, eid);
}
}
}
/*-----------------------------------------*/
/* Step 3: let the springs spring */
/*-----------------------------------------*/
maxchange = epsilon + 1;
while (it < maxit && maxchange > epsilon) {
long int jj;
igraph_real_t t = maxdelta * pow((maxit - it) / (double)maxit, coolexp);
long int vid, nei;
IGRAPH_PROGRESS("Large graph layout",
100.0 * ((actlayer - 1.0) / (no_of_layers - 1.0) + ((float)it) / (maxit * (no_of_layers - 1.0))),
0);
/* init */
igraph_vector_null(&forcex);
igraph_vector_null(&forcey);
maxchange = 0;
/* attractive "forces" along the edges */
for (jj = 0; jj < igraph_vector_size(&edges); jj++) {
igraph_integer_t from, to;
igraph_real_t xd, yd, dist, force;
IGRAPH_ALLOW_INTERRUPTION();
igraph_edge(graph, (igraph_integer_t) VECTOR(edges)[jj], &from, &to);
xd = MATRIX(*res, (long int)from, 0) - MATRIX(*res, (long int)to, 0);
yd = MATRIX(*res, (long int)from, 1) - MATRIX(*res, (long int)to, 1);
dist = sqrt(xd * xd + yd * yd);
if (dist != 0) {
xd /= dist;
yd /= dist;
}
force = dist * dist / frk;
VECTOR(forcex)[(long int)from] -= xd * force;
VECTOR(forcex)[(long int)to] += xd * force;
VECTOR(forcey)[(long int)from] -= yd * force;
VECTOR(forcey)[(long int)to] += yd * force;
}
/* repulsive "forces" of the vertices nearby */
pairs = 0;
igraph_2dgrid_reset(&grid, &vidit);
while ( (vid = igraph_2dgrid_next(&grid, &vidit) - 1) != -1) {
while ( (nei = igraph_2dgrid_next_nei(&grid, &vidit) - 1) != -1) {
igraph_real_t xd = MATRIX(*res, (long int)vid, 0) -
MATRIX(*res, (long int)nei, 0);
igraph_real_t yd = MATRIX(*res, (long int)vid, 1) -
MATRIX(*res, (long int)nei, 1);
igraph_real_t dist = sqrt(xd * xd + yd * yd);
igraph_real_t force;
if (dist < cellsize) {
pairs++;
if (dist == 0) {
dist = epsilon;
};
xd /= dist; yd /= dist;
force = frk * frk * (1.0 / dist - dist * dist / repulserad);
VECTOR(forcex)[(long int)vid] += xd * force;
VECTOR(forcex)[(long int)nei] -= xd * force;
VECTOR(forcey)[(long int)vid] += yd * force;
VECTOR(forcey)[(long int)nei] -= yd * force;
}
}
}
/* printf("verties: %li iterations: %li\n", */
/* (long int) VECTOR(layers)[actlayer+1], pairs); */
/* apply the changes */
for (jj = 0; jj < VECTOR(layers)[actlayer + 1]; jj++) {
long int vvid = (long int) VECTOR(vids)[jj];
igraph_real_t fx = VECTOR(forcex)[vvid];
igraph_real_t fy = VECTOR(forcey)[vvid];
igraph_real_t ded = sqrt(fx * fx + fy * fy);
if (ded > t) {
ded = t / ded;
fx *= ded; fy *= ded;
}
igraph_2dgrid_move(&grid, vvid, fx, fy);
if (fx > maxchange) {
maxchange = fx;
}
if (fy > maxchange) {
maxchange = fy;
}
}
it++;
/* printf("%li iterations, maxchange: %f\n", it, (double)maxchange); */
}
}
IGRAPH_PROGRESS("Large graph layout", 100.0, 0);
igraph_destroy(&mst);
igraph_vector_destroy(&vids);
igraph_vector_destroy(&layers);
igraph_vector_destroy(&parents);
igraph_vector_destroy(&edges);
igraph_2dgrid_destroy(&grid);
igraph_vector_destroy(&eids);
igraph_vector_destroy(&forcex);
igraph_vector_destroy(&forcey);
IGRAPH_FINALLY_CLEAN(9);
return 0;
}
static int igraph_i_layout_reingold_tilford_unreachable(
const igraph_t *graph,
igraph_neimode_t mode,
long int real_root,
long int no_of_nodes,
igraph_vector_t *pnewedges) {
long int no_of_newedges;
igraph_vector_t visited;
long int i, j, n;
igraph_dqueue_t q = IGRAPH_DQUEUE_NULL;
igraph_adjlist_t allneis;
igraph_vector_int_t *neis;
igraph_vector_resize(pnewedges, 0);
/* traverse from real_root and see what nodes you cannot reach */
no_of_newedges = 0;
IGRAPH_VECTOR_INIT_FINALLY(&visited, no_of_nodes);
IGRAPH_DQUEUE_INIT_FINALLY(&q, 100);
IGRAPH_CHECK(igraph_adjlist_init(graph, &allneis, mode));
IGRAPH_FINALLY(igraph_adjlist_destroy, &allneis);
/* start from real_root and go BFS */
IGRAPH_CHECK(igraph_dqueue_push(&q, real_root));
while (!igraph_dqueue_empty(&q)) {
long int actnode = (long int) igraph_dqueue_pop(&q);
neis = igraph_adjlist_get(&allneis, actnode);
n = igraph_vector_int_size(neis);
VECTOR(visited)[actnode] = 1;
for (j = 0; j < n; j++) {
long int neighbor = (long int) VECTOR(*neis)[j];
if (!(long int)VECTOR(visited)[neighbor]) {
IGRAPH_CHECK(igraph_dqueue_push(&q, neighbor));
}
}
}
for (j = 0; j < no_of_nodes; j++) {
no_of_newedges += 1 - VECTOR(visited)[j];
}
/* if any nodes are unreachable, add edges between them and real_root */
if (no_of_newedges != 0) {
igraph_vector_resize(pnewedges, no_of_newedges * 2);
j = 0;
for (i = 0; i < no_of_nodes; i++) {
if (!VECTOR(visited)[i]) {
if (mode != IGRAPH_IN) {
VECTOR(*pnewedges)[2 * j] = real_root;
VECTOR(*pnewedges)[2 * j + 1] = i;
} else {
VECTOR(*pnewedges)[2 * j] = i;
VECTOR(*pnewedges)[2 * j + 1] = real_root;
}
j++;
}
}
}
igraph_dqueue_destroy(&q);
igraph_adjlist_destroy(&allneis);
igraph_vector_destroy(&visited);
IGRAPH_FINALLY_CLEAN(3);
return IGRAPH_SUCCESS;
}
/* Internal structure for Reingold-Tilford layout */
struct igraph_i_reingold_tilford_vertex {
long int parent; /* Parent node index */
long int level; /* Level of the node */
igraph_real_t offset; /* X offset from parent node */
long int left_contour; /* Next left node of the contour
of the subtree rooted at this node */
long int right_contour; /* Next right node of the contour
of the subtree rooted at this node */
igraph_real_t offset_to_left_contour; /* X offset when following the left contour */
igraph_real_t offset_to_right_contour; /* X offset when following the right contour */
long int left_extreme; /* Leftmost node on the deepest layer of the subtree rooted at this node */
long int right_extreme; /* Rightmost node on the deepest layer of the subtree rooted at this node */
igraph_real_t offset_to_left_extreme; /* X offset when jumping to the left extreme node */
igraph_real_t offset_to_right_extreme; /* X offset when jumping to the right extreme node */
};
static int igraph_i_layout_reingold_tilford_postorder(struct igraph_i_reingold_tilford_vertex *vdata,
long int node, long int vcount);
static int igraph_i_layout_reingold_tilford_calc_coords(struct igraph_i_reingold_tilford_vertex *vdata,
igraph_matrix_t *res, long int node,
long int vcount, igraph_real_t xpos);
/* uncomment the next line for debugging the Reingold-Tilford layout */
/* #define LAYOUT_RT_DEBUG 1 */
static int igraph_i_layout_reingold_tilford(const igraph_t *graph,
igraph_matrix_t *res,
igraph_neimode_t mode,
long int root) {
long int no_of_nodes = igraph_vcount(graph);
long int i, n, j;
igraph_dqueue_t q = IGRAPH_DQUEUE_NULL;
igraph_adjlist_t allneis;
igraph_vector_int_t *neis;
struct igraph_i_reingold_tilford_vertex *vdata;
IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, 2));
IGRAPH_DQUEUE_INIT_FINALLY(&q, 100);
IGRAPH_CHECK(igraph_adjlist_init(graph, &allneis, mode));
IGRAPH_FINALLY(igraph_adjlist_destroy, &allneis);
vdata = igraph_Calloc(no_of_nodes, struct igraph_i_reingold_tilford_vertex);
if (vdata == 0) {
IGRAPH_ERROR("igraph_layout_reingold_tilford failed", IGRAPH_ENOMEM);
}
IGRAPH_FINALLY(igraph_free, vdata);
for (i = 0; i < no_of_nodes; i++) {
vdata[i].parent = -1;
vdata[i].level = -1;
vdata[i].offset = 0.0;
vdata[i].left_contour = -1;
vdata[i].right_contour = -1;
vdata[i].offset_to_left_contour = 0.0;
vdata[i].offset_to_right_contour = 0.0;
vdata[i].left_extreme = i;
vdata[i].right_extreme = i;
vdata[i].offset_to_left_extreme = 0.0;
vdata[i].offset_to_right_extreme = 0.0;
}
vdata[root].parent = root;
vdata[root].level = 0;
MATRIX(*res, root, 1) = 0;
/* Step 1: assign Y coordinates based on BFS and setup parents vector */
IGRAPH_CHECK(igraph_dqueue_push(&q, root));
IGRAPH_CHECK(igraph_dqueue_push(&q, 0));
while (!igraph_dqueue_empty(&q)) {
long int actnode = (long int) igraph_dqueue_pop(&q);
long int actdist = (long int) igraph_dqueue_pop(&q);
neis = igraph_adjlist_get(&allneis, actnode);
n = igraph_vector_int_size(neis);
for (j = 0; j < n; j++) {
long int neighbor = (long int) VECTOR(*neis)[j];
if (vdata[neighbor].parent >= 0) {
continue;
}
MATRIX(*res, neighbor, 1) = actdist + 1;
IGRAPH_CHECK(igraph_dqueue_push(&q, neighbor));
IGRAPH_CHECK(igraph_dqueue_push(&q, actdist + 1));
vdata[neighbor].parent = actnode;
vdata[neighbor].level = actdist + 1;
}
}
/* Step 2: postorder tree traversal, determines the appropriate X
* offsets for every node */
igraph_i_layout_reingold_tilford_postorder(vdata, root, no_of_nodes);
/* Step 3: calculate real coordinates based on X offsets */
igraph_i_layout_reingold_tilford_calc_coords(vdata, res, root, no_of_nodes, vdata[root].offset);
igraph_dqueue_destroy(&q);
igraph_adjlist_destroy(&allneis);
igraph_free(vdata);
IGRAPH_FINALLY_CLEAN(3);
IGRAPH_PROGRESS("Reingold-Tilford tree layout", 100.0, NULL);
#ifdef LAYOUT_RT_DEBUG
for (i = 0; i < no_of_nodes; i++) {
printf(
"%3ld: offset = %.2f, contours = [%ld, %ld], contour offsets = [%.2f, %.2f]\n",
i, vdata[i].offset,
vdata[i].left_contour, vdata[i].right_contour,
vdata[i].offset_to_left_contour, vdata[i].offset_to_right_contour
);
if (vdata[i].left_extreme != i || vdata[i].right_extreme != i) {
printf(
" extrema = [%ld, %ld], offsets to extrema = [%.2f, %.2f]\n",
vdata[i].left_extreme, vdata[i].right_extreme,
vdata[i].offset_to_left_extreme, vdata[i].offset_to_right_extreme
);
}
}
#endif
return 0;
}
static int igraph_i_layout_reingold_tilford_calc_coords(
struct igraph_i_reingold_tilford_vertex *vdata,
igraph_matrix_t *res, long int node,
long int vcount, igraph_real_t xpos) {
long int i;
MATRIX(*res, node, 0) = xpos;
for (i = 0; i < vcount; i++) {
if (i == node) {
continue;
}
if (vdata[i].parent == node) {
igraph_i_layout_reingold_tilford_calc_coords(vdata, res, i, vcount,
xpos + vdata[i].offset);
}
}
return 0;
}
static int igraph_i_layout_reingold_tilford_postorder(
struct igraph_i_reingold_tilford_vertex *vdata,
long int node, long int vcount) {
long int i, j, childcount, leftroot, leftrootidx;
const igraph_real_t minsep = 1;
igraph_real_t avg;
#ifdef LAYOUT_RT_DEBUG
printf("Starting visiting node %ld\n", node);
#endif
/* Check whether this node is a leaf node */
childcount = 0;
for (i = 0; i < vcount; i++) {
if (i == node) {
continue;
}
if (vdata[i].parent == node) {
/* Node i is a child, so visit it recursively */
childcount++;
igraph_i_layout_reingold_tilford_postorder(vdata, i, vcount);
}
}
if (childcount == 0) {
return 0;
}
/* Here we can assume that all of the subtrees have been placed and their
* left and right contours are calculated. Let's place them next to each
* other as close as we can.
* We will take each subtree in an arbitrary order. The root of the
* first one will be placed at offset 0, the next ones will be placed
* as close to each other as possible. leftroot stores the root of the
* rightmost subtree of the already placed subtrees - its right contour
* will be checked against the left contour of the next subtree */
leftroot = leftrootidx = -1;
avg = 0.0;
#ifdef LAYOUT_RT_DEBUG
printf("Visited node %ld and arranged its subtrees\n", node);
#endif
for (i = 0, j = 0; i < vcount; i++) {
if (i == node) {
continue;
}
if (vdata[i].parent == node) {
if (leftroot >= 0) {
/* Now we will follow the right contour of leftroot and the
* left contour of the subtree rooted at i */
long lnode, rnode, auxnode;
igraph_real_t loffset, roffset, rootsep, newoffset;
#ifdef LAYOUT_RT_DEBUG
printf(" Placing child %ld on level %ld, to the right of %ld\n", i, vdata[i].level, leftroot);
#endif
lnode = leftroot; rnode = i;
rootsep = vdata[leftroot].offset + minsep;
loffset = vdata[leftroot].offset; roffset = loffset + minsep;
/* Keep on updating the right contour now that we have attached
* a new node to the subtree being built */
vdata[node].right_contour = i;
vdata[node].offset_to_right_contour = rootsep;
#ifdef LAYOUT_RT_DEBUG
printf(" Contour: [%ld, %ld], offsets: [%lf, %lf], rootsep: %lf\n",
lnode, rnode, loffset, roffset, rootsep);
#endif
while ((lnode >= 0) && (rnode >= 0)) {
/* Step to the next level on the right contour of the left subtree */
if (vdata[lnode].right_contour >= 0) {
loffset += vdata[lnode].offset_to_right_contour;
lnode = vdata[lnode].right_contour;
} else {
/* Left subtree ended there. The left and right contour
* of the left subtree will continue to the next step
* on the right subtree. */
if (vdata[rnode].left_contour >= 0) {
auxnode = vdata[node].left_extreme;
/* this is the "threading" step that the original
* paper is talking about */
newoffset = (vdata[node].offset_to_right_extreme - vdata[node].offset_to_left_extreme) + minsep + vdata[rnode].offset_to_left_contour;
vdata[auxnode].left_contour = vdata[rnode].left_contour;
vdata[auxnode].right_contour = vdata[rnode].left_contour;
vdata[auxnode].offset_to_left_contour = vdata[auxnode].offset_to_right_contour = newoffset;
/* since we attached a larger subtree to the
* already placed left subtree, we need to update
* the extrema of the subtree rooted at 'node' */
vdata[node].left_extreme = vdata[i].left_extreme;
vdata[node].right_extreme = vdata[i].right_extreme;
vdata[node].offset_to_left_extreme = vdata[i].offset_to_left_extreme + rootsep;
vdata[node].offset_to_right_extreme = vdata[i].offset_to_right_extreme + rootsep;
#ifdef LAYOUT_RT_DEBUG
printf(" Left subtree ended earlier, continuing left subtree's left and right contour on right subtree (node %ld gets connected to node %ld)\n", auxnode, vdata[rnode].left_contour);
printf(" New contour following offset for node %ld is %lf\n", auxnode, vdata[auxnode].offset_to_left_contour);
#endif
} else {
/* Both subtrees are ending at the same time; the
* left extreme node of the subtree rooted at
* 'node' remains the same but the right extreme
* will change */
vdata[node].right_extreme = vdata[i].right_extreme;
vdata[node].offset_to_right_extreme = vdata[i].offset_to_right_extreme + rootsep;
}
lnode = -1;
}
/* Step to the next level on the left contour of the right subtree */
if (vdata[rnode].left_contour >= 0) {
roffset += vdata[rnode].offset_to_left_contour;
rnode = vdata[rnode].left_contour;
} else {
/* Right subtree ended here. The right contour of the right
* subtree will continue to the next step on the left subtree.
* Note that lnode has already been advanced here */
if (lnode >= 0) {
auxnode = vdata[i].right_extreme;
/* this is the "threading" step that the original
* paper is talking about */
newoffset = loffset - rootsep - vdata[i].offset_to_right_extreme;
vdata[auxnode].left_contour = lnode;
vdata[auxnode].right_contour = lnode;
vdata[auxnode].offset_to_left_contour = vdata[auxnode].offset_to_right_contour = newoffset;
/* no need to update the extrema of the subtree
* rooted at 'node' because the right subtree was
* smaller */
#ifdef LAYOUT_RT_DEBUG
printf(" Right subtree ended earlier, continuing right subtree's left and right contour on left subtree (node %ld gets connected to node %ld)\n", auxnode, lnode);
printf(" New contour following offset for node %ld is %lf\n", auxnode, vdata[auxnode].offset_to_left_contour);
#endif
}
rnode = -1;
}
#ifdef LAYOUT_RT_DEBUG
printf(" Contour: [%ld, %ld], offsets: [%lf, %lf], rootsep: %lf\n",
lnode, rnode, loffset, roffset, rootsep);
#endif
/* Push subtrees away if necessary */
if ((lnode >= 0) && (rnode >= 0) && (roffset - loffset < minsep)) {
#ifdef LAYOUT_RT_DEBUG
printf(" Pushing right subtree away by %lf\n", minsep-roffset+loffset);
#endif
rootsep += minsep - roffset + loffset;
roffset = loffset + minsep;
vdata[node].offset_to_right_contour = rootsep;
}
}
#ifdef LAYOUT_RT_DEBUG
printf(" Offset of subtree with root node %ld will be %lf\n", i, rootsep);
#endif
vdata[i].offset = rootsep;
vdata[node].offset_to_right_contour = rootsep;
avg = (avg * j) / (j + 1) + rootsep / (j + 1);
leftrootidx = j;
leftroot = i;
} else {
/* This is the first child of the node being considered so we
* can simply place the subtree on our virtual canvas */
#ifdef LAYOUT_RT_DEBUG
printf(" Placing child %ld on level %ld as first child\n", i, vdata[i].level);
#endif
leftrootidx = j;
leftroot = i;
vdata[node].left_contour = i;
vdata[node].right_contour = i;
vdata[node].offset_to_left_contour = 0.0;
vdata[node].offset_to_right_contour = 0.0;
vdata[node].left_extreme = vdata[i].left_extreme;
vdata[node].right_extreme = vdata[i].right_extreme;
vdata[node].offset_to_left_extreme = vdata[i].offset_to_left_extreme;
vdata[node].offset_to_right_extreme = vdata[i].offset_to_right_extreme;
avg = vdata[i].offset;
}
j++;
}
}
#ifdef LAYOUT_RT_DEBUG
printf("Shifting node %ld to be centered above children. Shift amount: %lf\n", node, avg);
#endif
vdata[node].offset_to_left_contour -= avg;
vdata[node].offset_to_right_contour -= avg;
vdata[node].offset_to_left_extreme -= avg;
vdata[node].offset_to_right_extreme -= avg;
for (i = 0, j = 0; i < vcount; i++) {
if (i == node) {
continue;
}
if (vdata[i].parent == node) {
vdata[i].offset -= avg;
}
}
return 0;
}
/**
* \function igraph_layout_reingold_tilford
* \brief Reingold-Tilford layout for tree graphs
*
* </para><para>
* Arranges the nodes in a tree where the given node is used as the root.
* The tree is directed downwards and the parents are centered above its
* children. For the exact algorithm, see:
*
* </para><para>
* Reingold, E and Tilford, J: Tidier drawing of trees.
* IEEE Trans. Softw. Eng., SE-7(2):223--228, 1981
*
* </para><para>
* If the given graph is not a tree, a breadth-first search is executed
* first to obtain a possible spanning tree.
*
* \param graph The graph object.
* \param res The result, the coordinates in a matrix. The parameter
* should point to an initialized matrix object and will be resized.
* \param mode Specifies which edges to consider when building the tree.
* If it is \c IGRAPH_OUT then only the outgoing, if it is \c IGRAPH_IN
* then only the incoming edges of a parent are considered. If it is
* \c IGRAPH_ALL then all edges are used (this was the behavior in
* igraph 0.5 and before). This parameter also influences how the root
* vertices are calculated, if they are not given. See the \p roots parameter.
* \param roots The index of the root vertex or root vertices.
* If this is a non-empty vector then the supplied vertex ids are used
* as the roots of the trees (or a single tree if the graph is connected).
* If it is a null pointer of a pointer to an empty vector, then the root
* vertices are automatically calculated based on topological sorting,
* performed with the opposite mode than the \p mode argument.
* After the vertices have been sorted, one is selected from each component.
* \param rootlevel This argument can be useful when drawing forests which are
* not trees (i.e. they are unconnected and have tree components). It specifies
* the level of the root vertices for every tree in the forest. It is only
* considered if not a null pointer and the \p roots argument is also given
* (and it is not a null pointer of an empty vector).
* \return Error code.
*
* Added in version 0.2.
*
* \sa \ref igraph_layout_reingold_tilford_circular().
*
* \example examples/simple/igraph_layout_reingold_tilford.c
*/
int igraph_layout_reingold_tilford(const igraph_t *graph,
igraph_matrix_t *res,
igraph_neimode_t mode,
const igraph_vector_t *roots,
const igraph_vector_t *rootlevel) {
long int no_of_nodes_orig = igraph_vcount(graph);
long int no_of_nodes = no_of_nodes_orig;
long int real_root;
igraph_t extended;
const igraph_t *pextended = graph;
igraph_vector_t myroots;
const igraph_vector_t *proots = roots;
igraph_neimode_t mode2;
long int i;
igraph_vector_t newedges;
/* TODO: possible speedup could be achieved if we use a table for storing
* the children of each node in the tree. (Now the implementation uses a
* single array containing the parent of each node and a node's children
* are determined by looking for other nodes that have this node as parent)
*/
/* at various steps it might be necessary to add edges to the graph */
IGRAPH_VECTOR_INIT_FINALLY(&newedges, 0);
if (!igraph_is_directed(graph)) {
mode = IGRAPH_ALL;
}
if ( (!roots || igraph_vector_size(roots) == 0) &&
rootlevel && igraph_vector_size(rootlevel) != 0 ) {
IGRAPH_WARNING("Reingold-Tilford layout: 'rootlevel' ignored");
}
/* ----------------------------------------------------------------------- */
/* If root vertices are not given, then do a topological sort and take
the last element from every component for directed graphs and mode == out,
or the first element from every component for directed graphs and mode ==
in,or select the vertex with the maximum degree from each component for
undirected graphs */
if (!roots || igraph_vector_size(roots) == 0) {
igraph_vector_t order, membership;
igraph_integer_t no_comps;
long int i, noseen = 0;
IGRAPH_VECTOR_INIT_FINALLY(&myroots, 0);
IGRAPH_VECTOR_INIT_FINALLY(&order, no_of_nodes);
IGRAPH_VECTOR_INIT_FINALLY(&membership, no_of_nodes);
if (mode != IGRAPH_ALL) {
/* look for roots by swimming against the stream */
mode2 = (mode == IGRAPH_IN) ? IGRAPH_OUT : IGRAPH_IN;
IGRAPH_CHECK(igraph_topological_sorting(graph, &order, mode2));
IGRAPH_CHECK(igraph_clusters(graph, &membership, /*csize=*/ 0,
&no_comps, IGRAPH_WEAK));
} else {
IGRAPH_CHECK(igraph_sort_vertex_ids_by_degree(graph, &order,
igraph_vss_all(), IGRAPH_ALL, 0, IGRAPH_ASCENDING, 0));
IGRAPH_CHECK(igraph_clusters(graph, &membership, /*csize=*/ 0,
&no_comps, IGRAPH_WEAK));
}
IGRAPH_CHECK(igraph_vector_resize(&myroots, no_comps));
/* go backwards and fill the roots vector with indices [1, no_of_nodes]
The index 0 is used to signal this root has not been found yet:
all indices are then decreased by one to [0, no_of_nodes - 1] */
igraph_vector_null(&myroots);
proots = &myroots;
for (i = no_of_nodes - 1; noseen < no_comps && i >= 0; i--) {
long int v = (long int) VECTOR(order)[i];
long int mem = (long int) VECTOR(membership)[v];
if (VECTOR(myroots)[mem] == 0) {
noseen += 1;
VECTOR(myroots)[mem] = v + 1;
}
}
for (i = 0; i < no_comps; i++) {
VECTOR(myroots)[i] -= 1;
}
igraph_vector_destroy(&membership);
igraph_vector_destroy(&order);
IGRAPH_FINALLY_CLEAN(2);
} else if (rootlevel && igraph_vector_size(rootlevel) > 0 &&
igraph_vector_size(roots) > 1) {
/* ----------------------------------------------------------------------- */
/* Many roots were given to us, check 'rootlevel' */
long int plus_levels = 0;
long int i;
if (igraph_vector_size(roots) != igraph_vector_size(rootlevel)) {
IGRAPH_ERROR("Reingold-Tilford: 'roots' and 'rootlevel' lengths differ",
IGRAPH_EINVAL);
}
/* count the rootlevels that are not zero */
for (i = 0; i < igraph_vector_size(roots); i++) {
plus_levels += VECTOR(*rootlevel)[i];
}
/* make copy of graph, add vertices/edges */
if (plus_levels != 0) {
long int edgeptr = 0;
pextended = &extended;
IGRAPH_CHECK(igraph_copy(&extended, graph));
IGRAPH_FINALLY(igraph_destroy, &extended);
IGRAPH_CHECK(igraph_add_vertices(&extended,
(igraph_integer_t) plus_levels, 0));
igraph_vector_resize(&newedges, plus_levels * 2);
for (i = 0; i < igraph_vector_size(roots); i++) {
long int rl = (long int) VECTOR(*rootlevel)[i];
long int rn = (long int) VECTOR(*roots)[i];
long int j;
/* zero-level roots don't get anything special */
if (rl == 0) {
continue;
}
/* for each nonzero-level root, add vertices
and edges at all levels [1, 2, .., rl]
piercing through the graph. If mode=="in"
they pierce the other way */
if (mode != IGRAPH_IN) {
VECTOR(newedges)[edgeptr++] = no_of_nodes;
VECTOR(newedges)[edgeptr++] = rn;
for (j = 0; j < rl - 1; j++) {
VECTOR(newedges)[edgeptr++] = no_of_nodes + 1;
VECTOR(newedges)[edgeptr++] = no_of_nodes;
no_of_nodes++;
}
} else {
VECTOR(newedges)[edgeptr++] = rn;
VECTOR(newedges)[edgeptr++] = no_of_nodes;
for (j = 0; j < rl - 1; j++) {
VECTOR(newedges)[edgeptr++] = no_of_nodes;
VECTOR(newedges)[edgeptr++] = no_of_nodes + 1;
no_of_nodes++;
}
}
/* move on to the next root */
VECTOR(*roots)[i] = no_of_nodes++;
}
/* actually add the edges to the graph */
IGRAPH_CHECK(igraph_add_edges(&extended, &newedges, 0));
}
}
/* We have root vertices now. If one or more nonzero-level roots were
chosen by the user, we have copied the graph and added a few vertices
and (directed) edges to connect those floating roots to nonfloating,
zero-level equivalent roots.
Below, the function
igraph_i_layout_reingold_tilford(pextended, res, mode, real_root)
calculates the actual rt coordinates of the graph. However, for
simplicity that function requires a connected graph and a single root.
For directed graphs, it needs not be strongly connected, however all
nodes must be reachable from the root following the stream (i.e. the
root must be a "mother vertex").
So before we call that function we have to make sure the (copied) graph
satisfies that condition. That requires:
1. if there is more than one root, defining a single real_root
2. if a real_root is defined, adding edges to connect all roots to it
3. ensure real_root is mother of the whole graph. If it is not,
add shortcut edges from real_root to any disconnected node for now.
NOTE: 3. could be done better, e.g. by topological sorting of some kind.
But for now it's ok like this.
*/
/* if there is only one root, no need for real_root */
if (igraph_vector_size(proots) == 1) {
real_root = (long int) VECTOR(*proots)[0];
if (real_root < 0 || real_root >= no_of_nodes) {
IGRAPH_ERROR("invalid vertex id", IGRAPH_EINVVID);
}
/* else, we need to make real_root */
} else {
long int no_of_newedges;
/* Make copy of the graph unless it exists already */
if (pextended == graph) {
pextended = &extended;
IGRAPH_CHECK(igraph_copy(&extended, graph));
IGRAPH_FINALLY(igraph_destroy, &extended);
}
/* add real_root to the vertices */
real_root = no_of_nodes;
IGRAPH_CHECK(igraph_add_vertices(&extended, 1, 0));
no_of_nodes++;
/* add edges from the roots to real_root */
no_of_newedges = igraph_vector_size(proots);
igraph_vector_resize(&newedges, no_of_newedges * 2);
for (i = 0; i < no_of_newedges; i++) {
VECTOR(newedges)[2 * i] = no_of_nodes - 1;
VECTOR(newedges)[2 * i + 1] = VECTOR(*proots)[i];
}
IGRAPH_CHECK(igraph_add_edges(&extended, &newedges, 0));
}
/* prepare edges to unreachable parts of the graph */
IGRAPH_CHECK(igraph_i_layout_reingold_tilford_unreachable(pextended, mode, real_root, no_of_nodes, &newedges));
if (igraph_vector_size(&newedges) != 0) {
/* Make copy of the graph unless it exists already */
if (pextended == graph) {
pextended = &extended;
IGRAPH_CHECK(igraph_copy(&extended, graph));
IGRAPH_FINALLY(igraph_destroy, &extended);
}
IGRAPH_CHECK(igraph_add_edges(&extended, &newedges, 0));
}
igraph_vector_destroy(&newedges);
IGRAPH_FINALLY_CLEAN(1);
/* ----------------------------------------------------------------------- */
/* Layout */
IGRAPH_CHECK(igraph_i_layout_reingold_tilford(pextended, res, mode, real_root));
/* Remove the new vertices from the layout */
if (no_of_nodes != no_of_nodes_orig) {
if (no_of_nodes - 1 == no_of_nodes_orig) {
IGRAPH_CHECK(igraph_matrix_remove_row(res, no_of_nodes_orig));
} else {
igraph_matrix_t tmp;
long int i;
IGRAPH_MATRIX_INIT_FINALLY(&tmp, no_of_nodes_orig, 2);
for (i = 0; i < no_of_nodes_orig; i++) {
MATRIX(tmp, i, 0) = MATRIX(*res, i, 0);
MATRIX(tmp, i, 1) = MATRIX(*res, i, 1);
}
IGRAPH_CHECK(igraph_matrix_update(res, &tmp));
igraph_matrix_destroy(&tmp);
IGRAPH_FINALLY_CLEAN(1);
}
}
if (pextended != graph) {
igraph_destroy(&extended);
IGRAPH_FINALLY_CLEAN(1);
}
/* Remove the roots vector if it was created by us */
if (proots != roots) {
igraph_vector_destroy(&myroots);
IGRAPH_FINALLY_CLEAN(1);
}
return 0;
}
/**
* \function igraph_layout_reingold_tilford_circular
* \brief Circular Reingold-Tilford layout for trees
*
* </para><para>
* This layout is almost the same as \ref igraph_layout_reingold_tilford(), but
* the tree is drawn in a circular way, with the root vertex in the center.
*
* \param graph The graph object.
* \param res The result, the coordinates in a matrix. The parameter
* should point to an initialized matrix object and will be resized.
* \param mode Specifies which edges to consider when building the tree.
* If it is \c IGRAPH_OUT then only the outgoing, if it is \c IGRAPH_IN
* then only the incoming edges of a parent are considered. If it is
* \c IGRAPH_ALL then all edges are used (this was the behavior in
* igraph 0.5 and before). This parameter also influences how the root
* vertices are calculated, if they are not given. See the \p roots parameter.
* \param roots The index of the root vertex or root vertices.
* If this is a non-empty vector then the supplied vertex ids are used
* as the roots of the trees (or a single tree if the graph is connected).
* If it is a null pointer of a pointer to an empty vector, then the root
* vertices are automatically calculated based on topological sorting,
* performed with the opposite mode than the \p mode argument.
* After the vertices have been sorted, one is selected from each component.
* \param rootlevel This argument can be useful when drawing forests which are
* not trees (i.e. they are unconnected and have tree components). It specifies
* the level of the root vertices for every tree in the forest. It is only
* considered if not a null pointer and the \p roots argument is also given
* (and it is not a null pointer of an empty vector). Note that if you supply
* a null pointer here and the graph has multiple components, all of the root
* vertices will be mapped to the origin of the coordinate system, which does
* not really make sense.
* \return Error code.
*
* \sa \ref igraph_layout_reingold_tilford().
*/
int igraph_layout_reingold_tilford_circular(const igraph_t *graph,
igraph_matrix_t *res,
igraph_neimode_t mode,
const igraph_vector_t *roots,
const igraph_vector_t *rootlevel) {
long int no_of_nodes = igraph_vcount(graph);
long int i;
igraph_real_t ratio = 2 * M_PI * (no_of_nodes - 1.0) / no_of_nodes;
igraph_real_t minx, maxx;
IGRAPH_CHECK(igraph_layout_reingold_tilford(graph, res, mode, roots, rootlevel));
if (no_of_nodes == 0) {
return 0;
}
minx = maxx = MATRIX(*res, 0, 0);
for (i = 1; i < no_of_nodes; i++) {
if (MATRIX(*res, i, 0) > maxx) {
maxx = MATRIX(*res, i, 0);
}
if (MATRIX(*res, i, 0) < minx) {
minx = MATRIX(*res, i, 0);
}
}
if (maxx > minx) {
ratio /= (maxx - minx);
}
for (i = 0; i < no_of_nodes; i++) {
igraph_real_t phi = (MATRIX(*res, i, 0) - minx) * ratio;
igraph_real_t r = MATRIX(*res, i, 1);
MATRIX(*res, i, 0) = r * cos(phi);
MATRIX(*res, i, 1) = r * sin(phi);
}
return 0;
}
#define COULOMBS_CONSTANT 8987500000.0
static igraph_real_t igraph_i_distance_between(
const igraph_matrix_t *c,
long int a, long int b);
static int igraph_i_determine_electric_axal_forces(
const igraph_matrix_t *pos,
igraph_real_t *x,
igraph_real_t *y,
igraph_real_t directed_force,
igraph_real_t distance,
long int other_node,
long int this_node);
static int igraph_i_apply_electrical_force(
const igraph_matrix_t *pos,
igraph_vector_t *pending_forces_x,
igraph_vector_t *pending_forces_y,
long int other_node, long int this_node,
igraph_real_t node_charge,
igraph_real_t distance);
static int igraph_i_determine_spring_axal_forces(
const igraph_matrix_t *pos,
igraph_real_t *x, igraph_real_t *y,
igraph_real_t directed_force,
igraph_real_t distance,
int spring_length,
long int other_node,
long int this_node);
static int igraph_i_apply_spring_force(
const igraph_matrix_t *pos,
igraph_vector_t *pending_forces_x,
igraph_vector_t *pending_forces_y,
long int other_node,
long int this_node, int spring_length,
igraph_real_t spring_constant);
static int igraph_i_move_nodes(
igraph_matrix_t *pos,
const igraph_vector_t *pending_forces_x,
const igraph_vector_t *pending_forces_y,
igraph_real_t node_mass,
igraph_real_t max_sa_movement);
static igraph_real_t igraph_i_distance_between(
const igraph_matrix_t *c,
long int a, long int b) {
igraph_real_t diffx = MATRIX(*c, a, 0) - MATRIX(*c, b, 0);
igraph_real_t diffy = MATRIX(*c, a, 1) - MATRIX(*c, b, 1);
return sqrt( diffx * diffx + diffy * diffy );
}
static int igraph_i_determine_electric_axal_forces(const igraph_matrix_t *pos,
igraph_real_t *x,
igraph_real_t *y,
igraph_real_t directed_force,
igraph_real_t distance,
long int other_node,
long int this_node) {
// We know what the directed force is. We now need to translate it
// into the appropriate x and y components.
// First, assume:
// other_node
// /|
// directed_force / |
// / | y
// /______|
// this_node x
//
// other_node.x > this_node.x
// other_node.y > this_node.y
// the force will be on this_node away from other_node
// the proportion (distance/y_distance) is equal to the proportion
// (directed_force/y_force), as the two triangles are similar.
// therefore, the magnitude of y_force = (directed_force*y_distance)/distance
// the sign of y_force is negative, away from other_node
igraph_real_t x_distance, y_distance;
y_distance = MATRIX(*pos, other_node, 1) - MATRIX(*pos, this_node, 1);
if (y_distance < 0) {
y_distance = -y_distance;
}
*y = -1 * ((directed_force * y_distance) / distance);
// the x component works in exactly the same way.
x_distance = MATRIX(*pos, other_node, 0) - MATRIX(*pos, this_node, 0);
if (x_distance < 0) {
x_distance = -x_distance;
}
*x = -1 * ((directed_force * x_distance) / distance);
// Now we need to reverse the polarity of our answers based on the falsness
// of our assumptions.
if (MATRIX(*pos, other_node, 0) < MATRIX(*pos, this_node, 0)) {
*x = *x * -1;
}
if (MATRIX(*pos, other_node, 1) < MATRIX(*pos, this_node, 1)) {
*y = *y * -1;
}
return 0;
}
static int igraph_i_apply_electrical_force(
const igraph_matrix_t *pos,
igraph_vector_t *pending_forces_x,
igraph_vector_t *pending_forces_y,
long int other_node, long int this_node,
igraph_real_t node_charge,
igraph_real_t distance) {
igraph_real_t directed_force = COULOMBS_CONSTANT *
((node_charge * node_charge) / (distance * distance));
igraph_real_t x_force, y_force;
igraph_i_determine_electric_axal_forces(pos, &x_force, &y_force,
directed_force, distance,
other_node, this_node);
VECTOR(*pending_forces_x)[this_node] += x_force;
VECTOR(*pending_forces_y)[this_node] += y_force;
VECTOR(*pending_forces_x)[other_node] -= x_force;
VECTOR(*pending_forces_y)[other_node] -= y_force;
return 0;
}
static int igraph_i_determine_spring_axal_forces(
const igraph_matrix_t *pos,
igraph_real_t *x, igraph_real_t *y,
igraph_real_t directed_force,
igraph_real_t distance,
int spring_length,
long int other_node, long int this_node) {
// if the spring is just the right size, the forces will be 0, so we can
// skip the computation.
//
// if the spring is too long, our forces will be identical to those computed
// by determine_electrical_axal_forces() (this_node will be pulled toward
// other_node).
//
// if the spring is too short, our forces will be the opposite of those
// computed by determine_electrical_axal_forces() (this_node will be pushed
// away from other_node)
//
// finally, since both nodes are movable, only one-half of the total force
// should be applied to each node, so half the forces for our answer.
if (distance == spring_length) {
*x = 0.0;
*y = 0.0;
} else {
igraph_i_determine_electric_axal_forces(pos, x, y, directed_force, distance,
other_node, this_node);
if (distance < spring_length) {
*x = -1 * *x;
*y = -1 * *y;
}
*x = 0.5 * *x;
*y = 0.5 * *y;
}
return 0;
}
static int igraph_i_apply_spring_force(
const igraph_matrix_t *pos,
igraph_vector_t *pending_forces_x,
igraph_vector_t *pending_forces_y,
long int other_node,
long int this_node, int spring_length,
igraph_real_t spring_constant) {
// determined using Hooke's Law:
// force = -kx
// where:
// k = spring constant
// x = displacement from ideal length in meters
igraph_real_t distance, displacement, directed_force, x_force, y_force;
distance = igraph_i_distance_between(pos, other_node, this_node);
// let's protect ourselves from division by zero by ignoring two nodes that
// happen to be in the same place. Since we separate all nodes before we
// work on any of them, this will only happen in extremely rare circumstances,
// and when it does, electrical force will probably push one or both of them
// one way or another anyway.
if (distance == 0.0) {
return 0;
}
displacement = distance - spring_length;
if (displacement < 0) {
displacement = -displacement;
}
directed_force = -1 * spring_constant * displacement;
// remember, this is force directed away from the spring;
// a negative number is back towards the spring (or, in our case, back towards
// the other node)
// get the force that should be applied to >this< node
igraph_i_determine_spring_axal_forces(pos, &x_force, &y_force,
directed_force, distance, spring_length,
other_node, this_node);
VECTOR(*pending_forces_x)[this_node] += x_force;
VECTOR(*pending_forces_y)[this_node] += y_force;
VECTOR(*pending_forces_x)[other_node] -= x_force;
VECTOR(*pending_forces_y)[other_node] -= y_force;
return 0;
}
static int igraph_i_move_nodes(
igraph_matrix_t *pos,
const igraph_vector_t *pending_forces_x,
const igraph_vector_t *pending_forces_y,
igraph_real_t node_mass,
igraph_real_t max_sa_movement) {
// Since each iteration is isolated, time is constant at 1.
// Therefore:
// Force effects acceleration.
// acceleration (d(velocity)/time) = velocity
// velocity (d(displacement)/time) = displacement
// displacement = acceleration
// determined using Newton's second law:
// sum(F) = ma
// therefore:
// acceleration = force / mass
// velocity = force / mass
// displacement = force / mass
long int this_node, no_of_nodes = igraph_vector_size(pending_forces_x);
for (this_node = 0; this_node < no_of_nodes; this_node++) {
igraph_real_t x_movement, y_movement;
x_movement = VECTOR(*pending_forces_x)[this_node] / node_mass;
if (x_movement > max_sa_movement) {
x_movement = max_sa_movement;
} else if (x_movement < -max_sa_movement) {
x_movement = -max_sa_movement;
}
y_movement = VECTOR(*pending_forces_y)[this_node] / node_mass;
if (y_movement > max_sa_movement) {
y_movement = max_sa_movement;
} else if (y_movement < -max_sa_movement) {
y_movement = -max_sa_movement;
}
MATRIX(*pos, this_node, 0) += x_movement;
MATRIX(*pos, this_node, 1) += y_movement;
}
return 0;
}
/**
* \function igraph_layout_graphopt
* \brief Optimizes vertex layout via the graphopt algorithm.
*
* </para><para>
* This is a port of the graphopt layout algorithm by Michael Schmuhl.
* graphopt version 0.4.1 was rewritten in C and the support for
* layers was removed (might be added later) and a code was a bit
* reorganized to avoid some unnecessary steps is the node charge (see below)
* is zero.
*
* </para><para>
* graphopt uses physical analogies for defining attracting and repelling
* forces among the vertices and then the physical system is simulated
* until it reaches an equilibrium. (There is no simulated annealing or
* anything like that, so a stable fixed point is not guaranteed.)
*
* </para><para>
* See also http://www.schmuhl.org/graphopt/ for the original graphopt.
* \param graph The input graph.
* \param res Pointer to an initialized matrix, the result will be stored here
* and its initial contents is used the starting point of the simulation
* if the \p use_seed argument is true. Note that in this case the
* matrix should have the proper size, otherwise a warning is issued and
* the supplied values are ignored. If no starting positions are given
* (or they are invalid) then a random staring position is used.
* The matrix will be resized if needed.
* \param niter Integer constant, the number of iterations to perform.
* Should be a couple of hundred in general. If you have a large graph
* then you might want to only do a few iterations and then check the
* result. If it is not good enough you can feed it in again in
* the \p res argument. The original graphopt default if 500.
* \param node_charge The charge of the vertices, used to calculate electric
* repulsion. The original graphopt default is 0.001.
* \param node_mass The mass of the vertices, used for the spring forces.
* The original graphopt defaults to 30.
* \param spring_length The length of the springs, an integer number.
* The original graphopt defaults to zero.
* \param spring_constant The spring constant, the original graphopt defaults
* to one.
* \param max_sa_movement Real constant, it gives the maximum amount of movement
* allowed in a single step along a single axis. The original graphopt
* default is 5.
* \param use_seed Logical scalar, whether to use the positions in \p res as
* a starting configuration. See also \p res above.
* \return Error code.
*
* Time complexity: O(n (|V|^2+|E|) ), n is the number of iterations,
* |V| is the number of vertices, |E| the number
* of edges. If \p node_charge is zero then it is only O(n|E|).
*/
int igraph_layout_graphopt(const igraph_t *graph, igraph_matrix_t *res,
igraph_integer_t niter,
igraph_real_t node_charge, igraph_real_t node_mass,
igraph_real_t spring_length,
igraph_real_t spring_constant,
igraph_real_t max_sa_movement,
igraph_bool_t use_seed) {
long int no_of_nodes = igraph_vcount(graph);
long int no_of_edges = igraph_ecount(graph);
int my_spring_length = (int) spring_length;
igraph_vector_t pending_forces_x, pending_forces_y;
/* Set a flag to calculate (or not) the electrical forces that the nodes */
/* apply on each other based on if both node types' charges are zero. */
igraph_bool_t apply_electric_charges = (node_charge != 0);
long int this_node, other_node, edge;
igraph_real_t distance;
long int i;
IGRAPH_VECTOR_INIT_FINALLY(&pending_forces_x, no_of_nodes);
IGRAPH_VECTOR_INIT_FINALLY(&pending_forces_y, no_of_nodes);
if (use_seed) {
if (igraph_matrix_nrow(res) != no_of_nodes ||
igraph_matrix_ncol(res) != 2) {
IGRAPH_WARNING("Invalid size for initial matrix, starting from random layout");
IGRAPH_CHECK(igraph_layout_random(graph, res));
}
} else {
IGRAPH_CHECK(igraph_layout_random(graph, res));
}
IGRAPH_PROGRESS("Graphopt layout", 0, NULL);
for (i = niter; i > 0; i--) {
/* Report progress in approx. every 100th step */
if (i % 10 == 0) {
IGRAPH_PROGRESS("Graphopt layout", 100.0 - 100.0 * i / niter, NULL);
}
/* Clear pending forces on all nodes */
igraph_vector_null(&pending_forces_x);
igraph_vector_null(&pending_forces_y);
// Apply electrical force applied by all other nodes
if (apply_electric_charges) {
// Iterate through all nodes
for (this_node = 0; this_node < no_of_nodes; this_node++) {
IGRAPH_ALLOW_INTERRUPTION();
for (other_node = this_node + 1;
other_node < no_of_nodes;
other_node++) {
distance = igraph_i_distance_between(res, this_node, other_node);
// let's protect ourselves from division by zero by ignoring
// two nodes that happen to be in the same place. Since we
// separate all nodes before we work on any of them, this
// will only happen in extremely rare circumstances, and when
// it does, springs will probably pull them apart anyway.
// also, if we are more than 50 away, the electric force
// will be negligible.
// ***** may not always be desirable ****
if ((distance != 0.0) && (distance < 500.0)) {
// if (distance != 0.0) {
// Apply electrical force from node(counter2) on
// node(counter)
igraph_i_apply_electrical_force(res, &pending_forces_x,
&pending_forces_y,
other_node, this_node,
node_charge,
distance);
}
}
}
}
// Apply force from springs
for (edge = 0; edge < no_of_edges; edge++) {
long int tthis_node = IGRAPH_FROM(graph, edge);
long int oother_node = IGRAPH_TO(graph, edge);
// Apply spring force on both nodes
igraph_i_apply_spring_force(res, &pending_forces_x, &pending_forces_y,
oother_node, tthis_node, my_spring_length,
spring_constant);
}
// Effect the movement of the nodes based on all pending forces
igraph_i_move_nodes(res, &pending_forces_x, &pending_forces_y, node_mass,
max_sa_movement);
}
IGRAPH_PROGRESS("Graphopt layout", 100, NULL);
igraph_vector_destroy(&pending_forces_y);
igraph_vector_destroy(&pending_forces_x);
IGRAPH_FINALLY_CLEAN(2);
return 0;
}
/* not 'static', used in tests */
int igraph_i_layout_merge_dla(igraph_i_layout_mergegrid_t *grid,
long int actg, igraph_real_t *x, igraph_real_t *y, igraph_real_t r,
igraph_real_t cx, igraph_real_t cy, igraph_real_t startr,
igraph_real_t killr);
/* TODO: not 'static' because used in tests */
int igraph_i_layout_sphere_2d(igraph_matrix_t *coords, igraph_real_t *x,
igraph_real_t *y, igraph_real_t *r);
int igraph_i_layout_sphere_3d(igraph_matrix_t *coords, igraph_real_t *x,
igraph_real_t *y, igraph_real_t *z,
igraph_real_t *r);
/**
* \function igraph_layout_merge_dla
* \brief Merge multiple layouts by using a DLA algorithm
*
* </para><para>
* First each layout is covered by a circle. Then the layout of the
* largest graph is placed at the origin. Then the other layouts are
* placed by the DLA algorithm, larger ones first and smaller ones
* last.
* \param thegraphs Pointer vector containing the graph object of
* which the layouts will be merged.
* \param coords Pointer vector containing matrix objects with the 2d
* layouts of the graphs in \p thegraphs.
* \param res Pointer to an initialized matrix object, the result will
* be stored here. It will be resized if needed.
* \return Error code.
*
* Added in version 0.2. This function is experimental.
*
* </para><para>
* Time complexity: TODO.
*/
int igraph_layout_merge_dla(igraph_vector_ptr_t *thegraphs,
igraph_vector_ptr_t *coords,
igraph_matrix_t *res) {
long int graphs = igraph_vector_ptr_size(coords);
igraph_vector_t sizes;
igraph_vector_t x, y, r;
igraph_vector_t nx, ny, nr;
long int allnodes = 0;
long int i, j;
long int actg;
igraph_i_layout_mergegrid_t grid;
long int jpos = 0;
igraph_real_t minx, maxx, miny, maxy;
igraph_real_t area = 0;
igraph_real_t maxr = 0;
long int respos;
/* Graphs are currently not used, only the coordinates */
IGRAPH_UNUSED(thegraphs);
IGRAPH_VECTOR_INIT_FINALLY(&sizes, graphs);
IGRAPH_VECTOR_INIT_FINALLY(&x, graphs);
IGRAPH_VECTOR_INIT_FINALLY(&y, graphs);
IGRAPH_VECTOR_INIT_FINALLY(&r, graphs);
IGRAPH_VECTOR_INIT_FINALLY(&nx, graphs);
IGRAPH_VECTOR_INIT_FINALLY(&ny, graphs);
IGRAPH_VECTOR_INIT_FINALLY(&nr, graphs);
RNG_BEGIN();
for (i = 0; i < igraph_vector_ptr_size(coords); i++) {
igraph_matrix_t *mat = VECTOR(*coords)[i];
long int size = igraph_matrix_nrow(mat);
if (igraph_matrix_ncol(mat) != 2) {
IGRAPH_ERROR("igraph_layout_merge_dla works for 2D layouts only",
IGRAPH_EINVAL);
}
IGRAPH_ALLOW_INTERRUPTION();
allnodes += size;
VECTOR(sizes)[i] = size;
VECTOR(r)[i] = pow(size, .75);
area += VECTOR(r)[i] * VECTOR(r)[i];
if (VECTOR(r)[i] > maxr) {
maxr = VECTOR(r)[i];
}
igraph_i_layout_sphere_2d(mat,
igraph_vector_e_ptr(&nx, i),
igraph_vector_e_ptr(&ny, i),
igraph_vector_e_ptr(&nr, i));
}
igraph_vector_order2(&sizes); /* largest first */
/* 0. create grid */
minx = miny = -sqrt(5 * area);
maxx = maxy = sqrt(5 * area);
igraph_i_layout_mergegrid_init(&grid, minx, maxx, 200,
miny, maxy, 200);
IGRAPH_FINALLY(igraph_i_layout_mergegrid_destroy, &grid);
/* fprintf(stderr, "Ok, starting DLA\n"); */
/* 1. place the largest */
actg = (long int) VECTOR(sizes)[jpos++];
igraph_i_layout_merge_place_sphere(&grid, 0, 0, VECTOR(r)[actg], actg);
IGRAPH_PROGRESS("Merging layouts via DLA", 0.0, NULL);
while (jpos < graphs) {
IGRAPH_ALLOW_INTERRUPTION();
/* fprintf(stderr, "comp: %li", jpos); */
IGRAPH_PROGRESS("Merging layouts via DLA", (100.0 * jpos) / graphs, NULL);
actg = (long int) VECTOR(sizes)[jpos++];
/* 2. random walk, TODO: tune parameters */
igraph_i_layout_merge_dla(&grid, actg,
igraph_vector_e_ptr(&x, actg),
igraph_vector_e_ptr(&y, actg),
VECTOR(r)[actg], 0, 0,
maxx, maxx + 5);
/* 3. place sphere */
igraph_i_layout_merge_place_sphere(&grid, VECTOR(x)[actg], VECTOR(y)[actg],
VECTOR(r)[actg], actg);
}
IGRAPH_PROGRESS("Merging layouts via DLA", 100.0, NULL);
/* Create the result */
IGRAPH_CHECK(igraph_matrix_resize(res, allnodes, 2));
respos = 0;
for (i = 0; i < graphs; i++) {
long int size = igraph_matrix_nrow(VECTOR(*coords)[i]);
igraph_real_t xx = VECTOR(x)[i];
igraph_real_t yy = VECTOR(y)[i];
igraph_real_t rr = VECTOR(r)[i] / VECTOR(nr)[i];
igraph_matrix_t *mat = VECTOR(*coords)[i];
IGRAPH_ALLOW_INTERRUPTION();
if (VECTOR(nr)[i] == 0) {
rr = 1;
}
for (j = 0; j < size; j++) {
MATRIX(*res, respos, 0) = rr * (MATRIX(*mat, j, 0) - VECTOR(nx)[i]);
MATRIX(*res, respos, 1) = rr * (MATRIX(*mat, j, 1) - VECTOR(ny)[i]);
MATRIX(*res, respos, 0) += xx;
MATRIX(*res, respos, 1) += yy;
++respos;
}
}
RNG_END();
igraph_i_layout_mergegrid_destroy(&grid);
igraph_vector_destroy(&sizes);
igraph_vector_destroy(&x);
igraph_vector_destroy(&y);
igraph_vector_destroy(&r);
igraph_vector_destroy(&nx);
igraph_vector_destroy(&ny);
igraph_vector_destroy(&nr);
IGRAPH_FINALLY_CLEAN(8);
return 0;
}
int igraph_i_layout_sphere_2d(igraph_matrix_t *coords,
igraph_real_t *x, igraph_real_t *y,
igraph_real_t *r) {
long int nodes = igraph_matrix_nrow(coords);
long int i;
igraph_real_t xmin, xmax, ymin, ymax;
xmin = xmax = MATRIX(*coords, 0, 0);
ymin = ymax = MATRIX(*coords, 0, 1);
for (i = 1; i < nodes; i++) {
if (MATRIX(*coords, i, 0) < xmin) {
xmin = MATRIX(*coords, i, 0);
} else if (MATRIX(*coords, i, 0) > xmax) {
xmax = MATRIX(*coords, i, 0);
}
if (MATRIX(*coords, i, 1) < ymin) {
ymin = MATRIX(*coords, i, 1);
} else if (MATRIX(*coords, i, 1) > ymax) {
ymax = MATRIX(*coords, i, 1);
}
}
*x = (xmin + xmax) / 2;
*y = (ymin + ymax) / 2;
*r = sqrt( (xmax - xmin) * (xmax - xmin) + (ymax - ymin) * (ymax - ymin) ) / 2;
return 0;
}
int igraph_i_layout_sphere_3d(igraph_matrix_t *coords,
igraph_real_t *x, igraph_real_t *y,
igraph_real_t *z, igraph_real_t *r) {
long int nodes = igraph_matrix_nrow(coords);
long int i;
igraph_real_t xmin, xmax, ymin, ymax, zmin, zmax;
xmin = xmax = MATRIX(*coords, 0, 0);
ymin = ymax = MATRIX(*coords, 0, 1);
zmin = zmax = MATRIX(*coords, 0, 2);
for (i = 1; i < nodes; i++) {
if (MATRIX(*coords, i, 0) < xmin) {
xmin = MATRIX(*coords, i, 0);
} else if (MATRIX(*coords, i, 0) > xmax) {
xmax = MATRIX(*coords, i, 0);
}
if (MATRIX(*coords, i, 1) < ymin) {
ymin = MATRIX(*coords, i, 1);
} else if (MATRIX(*coords, i, 1) > ymax) {
ymax = MATRIX(*coords, i, 1);
}
if (MATRIX(*coords, i, 2) < zmin) {
zmin = MATRIX(*coords, i, 2);
} else if (MATRIX(*coords, i, 2) > zmax) {
zmax = MATRIX(*coords, i, 2);
}
}
*x = (xmin + xmax) / 2;
*y = (ymin + ymax) / 2;
*z = (zmin + zmax) / 2;
*r = sqrt( (xmax - xmin) * (xmax - xmin) + (ymax - ymin) * (ymax - ymin) +
(zmax - zmin) * (zmax - zmin) ) / 2;
return 0;
}
#define DIST(x,y) (sqrt(pow((x)-cx,2)+pow((y)-cy,2)))
int igraph_i_layout_merge_dla(igraph_i_layout_mergegrid_t *grid,
long int actg, igraph_real_t *x, igraph_real_t *y, igraph_real_t r,
igraph_real_t cx, igraph_real_t cy, igraph_real_t startr,
igraph_real_t killr) {
long int sp = -1;
igraph_real_t angle, len;
long int steps = 0;
/* The graph is not used, only its coordinates */
IGRAPH_UNUSED(actg);
while (sp < 0) {
/* start particle */
do {
steps++;
angle = RNG_UNIF(0, 2 * M_PI);
len = RNG_UNIF(.5 * startr, startr);
*x = cx + len * cos(angle);
*y = cy + len * sin(angle);
sp = igraph_i_layout_mergegrid_get_sphere(grid, *x, *y, r);
} while (sp >= 0);
while (sp < 0 && DIST(*x, *y) < killr) {
igraph_real_t nx, ny;
steps++;
angle = RNG_UNIF(0, 2 * M_PI);
len = RNG_UNIF(0, startr / 100);
nx = *x + len * cos(angle);
ny = *y + len * sin(angle);
sp = igraph_i_layout_mergegrid_get_sphere(grid, nx, ny, r);
if (sp < 0) {
*x = nx; *y = ny;
}
}
}
/* fprintf(stderr, "%li ", steps); */
return 0;
}
static int igraph_i_layout_mds_step(igraph_real_t *to, const igraph_real_t *from,
int n, void *extra);
static int igraph_i_layout_mds_single(const igraph_t* graph, igraph_matrix_t *res,
igraph_matrix_t *dist, long int dim);
static int igraph_i_layout_mds_step(igraph_real_t *to, const igraph_real_t *from,
int n, void *extra) {
igraph_matrix_t* matrix = (igraph_matrix_t*)extra;
IGRAPH_UNUSED(n);
igraph_blas_dgemv_array(0, 1, matrix, from, 0, to);
return 0;
}
/* MDS layout for a connected graph, with no error checking on the
* input parameters. The distance matrix will be modified in-place. */
int igraph_i_layout_mds_single(const igraph_t* graph, igraph_matrix_t *res,
igraph_matrix_t *dist, long int dim) {
long int no_of_nodes = igraph_vcount(graph);
long int nev = dim;
igraph_matrix_t vectors;
igraph_vector_t values, row_means;
igraph_real_t grand_mean;
long int i, j, k;
igraph_eigen_which_t which;
/* Handle the trivial cases */
if (no_of_nodes == 1) {
IGRAPH_CHECK(igraph_matrix_resize(res, 1, dim));
igraph_matrix_fill(res, 0);
return IGRAPH_SUCCESS;
}
if (no_of_nodes == 2) {
IGRAPH_CHECK(igraph_matrix_resize(res, 2, dim));
igraph_matrix_fill(res, 0);
for (j = 0; j < dim; j++) {
MATRIX(*res, 1, j) = 1;
}
return IGRAPH_SUCCESS;
}
/* Initialize some stuff */
IGRAPH_VECTOR_INIT_FINALLY(&values, no_of_nodes);
IGRAPH_CHECK(igraph_matrix_init(&vectors, no_of_nodes, dim));
IGRAPH_FINALLY(igraph_matrix_destroy, &vectors);
/* Take the square of the distance matrix */
for (i = 0; i < no_of_nodes; i++) {
for (j = 0; j < no_of_nodes; j++) {
MATRIX(*dist, i, j) *= MATRIX(*dist, i, j);
}
}
/* Double centering of the distance matrix */
IGRAPH_VECTOR_INIT_FINALLY(&row_means, no_of_nodes);
igraph_vector_fill(&values, 1.0 / no_of_nodes);
igraph_blas_dgemv(0, 1, dist, &values, 0, &row_means);
grand_mean = igraph_vector_sum(&row_means) / no_of_nodes;
igraph_matrix_add_constant(dist, grand_mean);
for (i = 0; i < no_of_nodes; i++) {
for (j = 0; j < no_of_nodes; j++) {
MATRIX(*dist, i, j) -= VECTOR(row_means)[i] + VECTOR(row_means)[j];
MATRIX(*dist, i, j) *= -0.5;
}
}
igraph_vector_destroy(&row_means);
IGRAPH_FINALLY_CLEAN(1);
/* Calculate the top `dim` eigenvectors. */
which.pos = IGRAPH_EIGEN_LA;
which.howmany = (int) nev;
IGRAPH_CHECK(igraph_eigen_matrix_symmetric(/*A=*/ 0, /*sA=*/ 0,
/*fun=*/ igraph_i_layout_mds_step,
/*n=*/ (int) no_of_nodes, /*extra=*/ dist,
/*algorithm=*/ IGRAPH_EIGEN_LAPACK,
&which, /*options=*/ 0, /*storage=*/ 0,
&values, &vectors));
/* Calculate and normalize the final coordinates */
for (j = 0; j < nev; j++) {
VECTOR(values)[j] = sqrt(fabs(VECTOR(values)[j]));
}
IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, dim));
for (i = 0; i < no_of_nodes; i++) {
for (j = 0, k = nev - 1; j < nev; j++, k--) {
MATRIX(*res, i, k) = VECTOR(values)[j] * MATRIX(vectors, i, j);
}
}
igraph_matrix_destroy(&vectors);
igraph_vector_destroy(&values);
IGRAPH_FINALLY_CLEAN(2);
return IGRAPH_SUCCESS;
}
/**
* \function igraph_layout_mds
* \brief Place the vertices on a plane using multidimensional scaling.
*
* </para><para>
* This layout requires a distance matrix, where the intersection of
* row i and column j specifies the desired distance between vertex i
* and vertex j. The algorithm will try to place the vertices in a
* space having a given number of dimensions in a way that approximates
* the distance relations prescribed in the distance matrix. igraph
* uses the classical multidimensional scaling by Torgerson; for more
* details, see Cox & Cox: Multidimensional Scaling (1994), Chapman
* and Hall, London.
*
* </para><para>
* If the input graph is disconnected, igraph will decompose it
* first into its subgraphs, lay out the subgraphs one by one
* using the appropriate submatrices of the distance matrix, and
* then merge the layouts using \ref igraph_layout_merge_dla.
* Since \ref igraph_layout_merge_dla works for 2D layouts only,
* you cannot run the MDS layout on disconnected graphs for
* more than two dimensions.
*
* </para><para>
* Warning: if the graph is symmetric to the exchange of two vertices
* (as is the case with leaves of a tree connecting to the same parent),
* classical multidimensional scaling may assign the same coordinates to
* these vertices.
*
* \param graph A graph object.
* \param res Pointer to an initialized matrix object. This will
* contain the result and will be resized if needed.
* \param dist The distance matrix. It must be symmetric and this
* function does not check whether the matrix is indeed
* symmetric. Results are unspecified if you pass a non-symmetric
* matrix here. You can set this parameter to null; in this
* case, the shortest path lengths between vertices will be
* used as distances.
* \param dim The number of dimensions in the embedding space. For
* 2D layouts, supply 2 here.
* \param options This argument is currently ignored, it was used for
* ARPACK, but LAPACK is used now for calculating the eigenvectors.
* \return Error code.
*
* Added in version 0.6.
*
* </para><para>
* Time complexity: usually around O(|V|^2 dim).
*/
int igraph_layout_mds(const igraph_t* graph, igraph_matrix_t *res,
const igraph_matrix_t *dist, long int dim,
igraph_arpack_options_t *options) {
long int i, no_of_nodes = igraph_vcount(graph);
igraph_matrix_t m;
igraph_bool_t conn;
RNG_BEGIN();
/* Check the distance matrix */
if (dist && (igraph_matrix_nrow(dist) != no_of_nodes ||
igraph_matrix_ncol(dist) != no_of_nodes)) {
IGRAPH_ERROR("invalid distance matrix size", IGRAPH_EINVAL);
}
/* Check the number of dimensions */
if (dim <= 1) {
IGRAPH_ERROR("dim must be positive", IGRAPH_EINVAL);
}
if (dim > no_of_nodes) {
IGRAPH_ERROR("dim must be less than the number of nodes", IGRAPH_EINVAL);
}
/* Copy or obtain the distance matrix */
if (dist == 0) {
IGRAPH_CHECK(igraph_matrix_init(&m, no_of_nodes, no_of_nodes));
IGRAPH_FINALLY(igraph_matrix_destroy, &m);
IGRAPH_CHECK(igraph_shortest_paths(graph, &m,
igraph_vss_all(), igraph_vss_all(), IGRAPH_ALL));
} else {
IGRAPH_CHECK(igraph_matrix_copy(&m, dist));
IGRAPH_FINALLY(igraph_matrix_destroy, &m);
/* Make sure that the diagonal contains zeroes only */
for (i = 0; i < no_of_nodes; i++) {
MATRIX(m, i, i) = 0.0;
}
}
/* Check whether the graph is connected */
IGRAPH_CHECK(igraph_is_connected(graph, &conn, IGRAPH_WEAK));
if (conn) {
/* Yes, it is, just do the MDS */
IGRAPH_CHECK(igraph_i_layout_mds_single(graph, res, &m, dim));
} else {
/* The graph is not connected, lay out the components one by one */
igraph_vector_ptr_t layouts;
igraph_vector_t comp, vertex_order;
igraph_t subgraph;
igraph_matrix_t *layout;
igraph_matrix_t dist_submatrix;
igraph_bool_t *seen_vertices;
long int j, n, processed_vertex_count = 0;
IGRAPH_VECTOR_INIT_FINALLY(&comp, 0);
IGRAPH_VECTOR_INIT_FINALLY(&vertex_order, no_of_nodes);
IGRAPH_CHECK(igraph_vector_ptr_init(&layouts, 0));
IGRAPH_FINALLY(igraph_vector_ptr_destroy_all, &layouts);
igraph_vector_ptr_set_item_destructor(&layouts, (igraph_finally_func_t*)igraph_matrix_destroy);
IGRAPH_CHECK(igraph_matrix_init(&dist_submatrix, 0, 0));
IGRAPH_FINALLY(igraph_matrix_destroy, &dist_submatrix);
seen_vertices = igraph_Calloc(no_of_nodes, igraph_bool_t);
if (seen_vertices == 0) {
IGRAPH_ERROR("cannot calculate MDS layout", IGRAPH_ENOMEM);
}
IGRAPH_FINALLY(igraph_free, seen_vertices);
for (i = 0; i < no_of_nodes; i++) {
if (seen_vertices[i]) {
continue;
}
/* This is a vertex whose component we did not lay out so far */
IGRAPH_CHECK(igraph_subcomponent(graph, &comp, i, IGRAPH_ALL));
/* Take the subgraph */
IGRAPH_CHECK(igraph_induced_subgraph(graph, &subgraph, igraph_vss_vector(&comp),
IGRAPH_SUBGRAPH_AUTO));
IGRAPH_FINALLY(igraph_destroy, &subgraph);
/* Calculate the submatrix of the distances */
IGRAPH_CHECK(igraph_matrix_select_rows_cols(&m, &dist_submatrix,
&comp, &comp));
/* Allocate a new matrix for storing the layout */
layout = igraph_Calloc(1, igraph_matrix_t);
if (layout == 0) {
IGRAPH_ERROR("cannot calculate MDS layout", IGRAPH_ENOMEM);
}
IGRAPH_FINALLY(igraph_free, layout);
IGRAPH_CHECK(igraph_matrix_init(layout, 0, 0));
IGRAPH_FINALLY(igraph_matrix_destroy, layout);
/* Lay out the subgraph */
IGRAPH_CHECK(igraph_i_layout_mds_single(&subgraph, layout, &dist_submatrix, dim));
/* Store the layout */
IGRAPH_CHECK(igraph_vector_ptr_push_back(&layouts, layout));
IGRAPH_FINALLY_CLEAN(2); /* ownership of layout taken by layouts */
/* Free the newly created subgraph */
igraph_destroy(&subgraph);
IGRAPH_FINALLY_CLEAN(1);
/* Mark all the vertices in the component as visited */
n = igraph_vector_size(&comp);
for (j = 0; j < n; j++) {
seen_vertices[(long int)VECTOR(comp)[j]] = 1;
VECTOR(vertex_order)[(long int)VECTOR(comp)[j]] = processed_vertex_count++;
}
}
/* Merge the layouts - reusing dist_submatrix here */
IGRAPH_CHECK(igraph_layout_merge_dla(0, &layouts, &dist_submatrix));
/* Reordering the rows of res to match the original graph */
IGRAPH_CHECK(igraph_matrix_select_rows(&dist_submatrix, res, &vertex_order));
igraph_free(seen_vertices);
igraph_matrix_destroy(&dist_submatrix);
igraph_vector_ptr_destroy_all(&layouts);
igraph_vector_destroy(&vertex_order);
igraph_vector_destroy(&comp);
IGRAPH_FINALLY_CLEAN(5);
}
RNG_END();
igraph_matrix_destroy(&m);
IGRAPH_FINALLY_CLEAN(1);
return IGRAPH_SUCCESS;
}
/**
* \function igraph_layout_bipartite
* Simple layout for bipartite graphs
*
* The layout is created by first placing the vertices in two rows,
* according to their types. Then the positions within the rows are
* optimized to minimize edge crossings, by calling \ref
* igraph_layout_sugiyama().
*
* \param graph The input graph.
* \param types A boolean vector containing ones and zeros, the vertex
* types. Its length must match the number of vertices in the graph.
* \param res Pointer to an initialized matrix, the result, the x and
* y coordinates are stored here.
* \param hgap The preferred minimum horizontal gap between vertices
* in the same layer (i.e. vertices of the same type).
* \param vgap The distance between layers.
* \param maxiter Maximum number of iterations in the crossing
* minimization stage. 100 is a reasonable default; if you feel
* that you have too many edge crossings, increase this.
* \return Error code.
*
* \sa \ref igraph_layout_sugiyama().
*/
int igraph_layout_bipartite(const igraph_t *graph,
const igraph_vector_bool_t *types,
igraph_matrix_t *res, igraph_real_t hgap,
igraph_real_t vgap, long int maxiter) {
long int i, no_of_nodes = igraph_vcount(graph);
igraph_vector_t layers;
if (igraph_vector_bool_size(types) != no_of_nodes) {
IGRAPH_ERROR("Invalid vertex type vector size", IGRAPH_EINVAL);
}
IGRAPH_VECTOR_INIT_FINALLY(&layers, no_of_nodes);
for (i = 0; i < no_of_nodes; i++) {
VECTOR(layers)[i] = 1 - VECTOR(*types)[i];
}
IGRAPH_CHECK(igraph_layout_sugiyama(graph, res, /*extd_graph=*/ 0,
/*extd_to_orig_eids=*/ 0, &layers, hgap,
vgap, maxiter, /*weights=*/ 0));
igraph_vector_destroy(&layers);
IGRAPH_FINALLY_CLEAN(1);
return 0;
}