constrained_majorization_ipsep.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. /**
  2. *
  3. * Authors:
  4. * Tim Dwyer <[email protected]>
  5. *
  6. * Copyright (C) 2005 Authors
  7. *
  8. * This version is released under the CPL (Common Public License) with
  9. * the Graphviz distribution.
  10. * A version is also available under the LGPL as part of the Adaptagrams
  11. * project: http://sourceforge.net/projects/adaptagrams.
  12. * If you make improvements or bug fixes to this code it would be much
  13. * appreciated if you could also contribute those changes back to the
  14. * Adaptagrams repository.
  15. */
  16. /**********************************************************
  17. * Based on constrained_majorization.c
  18. *
  19. * Perform stress majorization subject
  20. * to separation constraints, for background see the paper:
  21. * "IPSep-CoLa: An Incremental Procedure for Separation Constraint Layout of Graphs"
  22. * by Tim Dwyer, Yehuda Koren and Kim Marriott
  23. *
  24. * Available separation constraints so far are:
  25. * o Directed edge constraints
  26. * o Node non-overlap constraints
  27. * o Cluster containment constraints
  28. * o Cluster/node non-overlap constraints
  29. *
  30. * Tim Dwyer, 2006
  31. **********************************************************/
  32. #include <neatogen/digcola.h>
  33. #include <stdbool.h>
  34. #include <util/alloc.h>
  35. #ifdef IPSEPCOLA
  36. #include <math.h>
  37. #include <stdlib.h>
  38. #include <time.h>
  39. #include <stdio.h>
  40. #include <float.h>
  41. #include <neatogen/stress.h>
  42. #include <neatogen/dijkstra.h>
  43. #include <neatogen/bfs.h>
  44. #include <neatogen/matrix_ops.h>
  45. #include <neatogen/kkutils.h>
  46. #include <neatogen/conjgrad.h>
  47. #include <vpsc/csolve_VPSC.h>
  48. #include <neatogen/quad_prog_vpsc.h>
  49. #include <neatogen/quad_prog_solver.h>
  50. #include <neatogen/matrix_ops.h>
  51. #define localConstrMajorIterations 1000
  52. int stress_majorization_cola(vtx_data * graph, /* Input graph in sparse representation */
  53. int n, /* Number of nodes */
  54. double **d_coords, /* Coordinates of nodes (output layout) */
  55. node_t ** nodes, /* Original nodes */
  56. int dim, /* Dimemsionality of layout */
  57. int model, /* difference model */
  58. int maxi, /* max iterations */
  59. ipsep_options * opt)
  60. {
  61. int iterations = 0; /* Output: number of iteration of the process */
  62. /*************************************************
  63. ** Computation of full, dense, unrestricted k-D **
  64. ** stress minimization by majorization **
  65. ** This function imposes HIERARCHY CONSTRAINTS **
  66. *************************************************/
  67. int i, k;
  68. float *lap1 = NULL;
  69. float *dist_accumulator = NULL;
  70. float *tmp_coords = NULL;
  71. float **b = NULL;
  72. double *degrees = NULL;
  73. float *lap2 = NULL;
  74. int lap_length;
  75. float *f_storage = NULL;
  76. float **coords = NULL;
  77. int orig_n = n;
  78. CMajEnvVPSC *cMajEnvHor = NULL;
  79. CMajEnvVPSC *cMajEnvVrt = NULL;
  80. double y_0;
  81. int length;
  82. DistType diameter;
  83. float *Dij = NULL;
  84. float constant_term;
  85. int count;
  86. double degree;
  87. int step;
  88. float val;
  89. double old_stress, new_stress = 0;
  90. bool converged;
  91. int len;
  92. double nsizeScale = 0;
  93. float maxEdgeLen = 0;
  94. double max = 1;
  95. initLayout(n, dim, d_coords, nodes);
  96. if (n == 1)
  97. return 0;
  98. for (i = 0; i < n; i++) {
  99. for (size_t j = 1; j < graph[i].nedges; j++) {
  100. maxEdgeLen = MAX(graph[i].ewgts[j], maxEdgeLen);
  101. }
  102. }
  103. /****************************************************
  104. ** Compute the all-pairs-shortest-distances matrix **
  105. ****************************************************/
  106. if (maxi == 0)
  107. return iterations;
  108. if (Verbose)
  109. start_timer();
  110. if (model == MODEL_SUBSET) {
  111. /* weight graph to separate high-degree nodes */
  112. /* and perform slower Dijkstra-based computation */
  113. if (Verbose)
  114. fprintf(stderr, "Calculating subset model");
  115. Dij = compute_apsp_artificial_weights_packed(graph, n);
  116. } else if (model == MODEL_CIRCUIT) {
  117. Dij = circuitModel(graph, n);
  118. if (!Dij) {
  119. agwarningf(
  120. "graph is disconnected. Hence, the circuit model\n");
  121. agerr(AGPREV,
  122. "is undefined. Reverting to the shortest path model.\n");
  123. }
  124. } else if (model == MODEL_MDS) {
  125. if (Verbose)
  126. fprintf(stderr, "Calculating MDS model");
  127. Dij = mdsModel(graph, n);
  128. }
  129. if (!Dij) {
  130. if (Verbose)
  131. fprintf(stderr, "Calculating shortest paths");
  132. Dij = compute_apsp_packed(graph, n);
  133. }
  134. if (Verbose) {
  135. fprintf(stderr, ": %.2f sec\n", elapsed_sec());
  136. fprintf(stderr, "Setting initial positions");
  137. start_timer();
  138. }
  139. diameter = -1;
  140. length = n + n * (n - 1) / 2;
  141. for (i = 0; i < length; i++) {
  142. if (Dij[i] > diameter) {
  143. diameter = (int) Dij[i];
  144. }
  145. }
  146. /* for numerical stability, scale down layout */
  147. /* No Jiggling, might conflict with constraints */
  148. for (i = 0; i < dim; i++) {
  149. for (int j = 0; j < n; j++) {
  150. max = fmax(max, fabs(d_coords[i][j]));
  151. }
  152. }
  153. for (i = 0; i < dim; i++) {
  154. for (int j = 0; j < n; j++) {
  155. d_coords[i][j] *= 10 / max;
  156. }
  157. }
  158. /**************************
  159. ** Layout initialization **
  160. **************************/
  161. for (i = 0; i < dim; i++) {
  162. orthog1(n, d_coords[i]);
  163. }
  164. /* for the y-coords, don't center them, but translate them so y[0]=0 */
  165. y_0 = d_coords[1][0];
  166. for (i = 0; i < n; i++) {
  167. d_coords[1][i] -= y_0;
  168. }
  169. if (Verbose)
  170. fprintf(stderr, ": %.2f sec", elapsed_sec());
  171. /**************************
  172. ** Laplacian computation **
  173. **************************/
  174. lap2 = Dij;
  175. lap_length = n + n * (n - 1) / 2;
  176. square_vec(lap_length, lap2);
  177. /* compute off-diagonal entries */
  178. invert_vec(lap_length, lap2);
  179. if (opt->clusters.nclusters > 0) {
  180. int nn = n + opt->clusters.nclusters * 2;
  181. int clap_length = nn + nn * (nn - 1) / 2;
  182. float *clap = gv_calloc(clap_length, sizeof(float));
  183. int c0, c1;
  184. float v;
  185. c0 = c1 = 0;
  186. for (i = 0; i < nn; i++) {
  187. for (int j = 0; j < nn - i; j++) {
  188. if (i < n && j < n - i) {
  189. v = lap2[c0++];
  190. } else {
  191. /* v=j==1?i%2:0; */
  192. if (j == 1 && i % 2 == 1) {
  193. v = maxEdgeLen;
  194. v *= v;
  195. if (v > 0.01f) {
  196. v = 1.0f / v;
  197. }
  198. } else
  199. v = 0;
  200. }
  201. clap[c1++] = v;
  202. }
  203. }
  204. free(lap2);
  205. lap2 = clap;
  206. n = nn;
  207. lap_length = clap_length;
  208. }
  209. /* compute diagonal entries */
  210. count = 0;
  211. degrees = gv_calloc(n, sizeof(double));
  212. set_vector_val(n, 0, degrees);
  213. for (i = 0; i < n - 1; i++) {
  214. degree = 0;
  215. count++; /* skip main diag entry */
  216. for (int j = 1; j < n - i; j++, count++) {
  217. val = lap2[count];
  218. degree += val;
  219. degrees[i + j] -= val;
  220. }
  221. degrees[i] -= degree;
  222. }
  223. for (step = n, count = 0, i = 0; i < n; i++, count += step, step--) {
  224. lap2[count] = (float) degrees[i];
  225. }
  226. coords = gv_calloc(dim, sizeof(float *));
  227. f_storage = gv_calloc(dim * n, sizeof(float));
  228. for (i = 0; i < dim; i++) {
  229. coords[i] = f_storage + i * n;
  230. for (int j = 0; j < n; j++) {
  231. coords[i][j] = j < orig_n ? (float)d_coords[i][j] : 0;
  232. }
  233. }
  234. /* compute constant term in stress sum
  235. * which is \sum_{i<j} w_{ij}d_{ij}^2
  236. */
  237. constant_term = (float) (n * (n - 1) / 2);
  238. /*************************
  239. ** Layout optimization **
  240. *************************/
  241. b = gv_calloc(dim, sizeof(float *));
  242. b[0] = gv_calloc(dim * n, sizeof(float));
  243. for (k = 1; k < dim; k++) {
  244. b[k] = b[0] + k * n;
  245. }
  246. tmp_coords = gv_calloc(n, sizeof(float));
  247. dist_accumulator = gv_calloc(n, sizeof(float));
  248. old_stress = DBL_MAX; /* at least one iteration */
  249. if ((cMajEnvHor = initCMajVPSC(n, lap2, graph, opt, 0)) == NULL) {
  250. iterations = -1;
  251. goto finish;
  252. }
  253. if ((cMajEnvVrt = initCMajVPSC(n, lap2, graph, opt, opt->diredges)) == NULL) {
  254. iterations = -1;
  255. goto finish;
  256. }
  257. lap1 = gv_calloc(lap_length, sizeof(float));
  258. for (converged = false, iterations = 0;
  259. iterations < maxi && !converged; iterations++) {
  260. /* First, construct Laplacian of 1/(d_ij*|p_i-p_j|) */
  261. set_vector_val(n, 0, degrees);
  262. sqrt_vecf(lap_length, lap2, lap1);
  263. for (count = 0, i = 0; i < n - 1; i++) {
  264. len = n - i - 1;
  265. /* init 'dist_accumulator' with zeros */
  266. set_vector_valf(n, 0, dist_accumulator);
  267. /* put into 'dist_accumulator' all squared distances
  268. * between 'i' and 'i'+1,...,'n'-1
  269. */
  270. for (k = 0; k < dim; k++) {
  271. set_vector_valf(len, coords[k][i], tmp_coords);
  272. vectors_mult_additionf(len, tmp_coords, -1, coords[k] + i + 1);
  273. square_vec(len, tmp_coords);
  274. vectors_additionf(len, tmp_coords, dist_accumulator, dist_accumulator);
  275. }
  276. /* convert to 1/d_{ij} */
  277. invert_sqrt_vec(len, dist_accumulator);
  278. /* detect overflows */
  279. for (int j = 0; j < len; j++) {
  280. if (dist_accumulator[j] >= FLT_MAX || dist_accumulator[j] < 0) {
  281. dist_accumulator[j] = 0;
  282. }
  283. }
  284. count++; /* save place for the main diagonal entry */
  285. degree = 0;
  286. for (int j = 0; j < len; j++, count++) {
  287. val = lap1[count] *= dist_accumulator[j];
  288. degree += val;
  289. degrees[i + j + 1] -= val;
  290. }
  291. degrees[i] -= degree;
  292. }
  293. for (step = n, count = 0, i = 0; i < n; i++, count += step, step--) {
  294. lap1[count] = (float) degrees[i];
  295. }
  296. /* Now compute b[] (L^(X(t))*X(t)) */
  297. for (k = 0; k < dim; k++) {
  298. /* b[k] := lap1*coords[k] */
  299. right_mult_with_vector_ff(lap1, n, coords[k], b[k]);
  300. }
  301. /* compute new stress
  302. * remember that the Laplacians are negated, so we subtract
  303. * instead of add and vice versa
  304. */
  305. new_stress = 0;
  306. for (k = 0; k < dim; k++) {
  307. new_stress += vectors_inner_productf(n, coords[k], b[k]);
  308. }
  309. new_stress *= 2;
  310. new_stress += constant_term; /* only after mult by 2 */
  311. for (k = 0; k < dim; k++) {
  312. right_mult_with_vector_ff(lap2, n, coords[k], tmp_coords);
  313. new_stress -= vectors_inner_productf(n, coords[k], tmp_coords);
  314. }
  315. /* check for convergence */
  316. if (Verbose && (iterations % 1 == 0)) {
  317. fprintf(stderr, "%.3f ", new_stress);
  318. if (iterations % 10 == 0)
  319. fprintf(stderr, "\n");
  320. }
  321. converged = new_stress < old_stress
  322. && fabs(new_stress - old_stress) / fabs(old_stress + 1e-10) <
  323. Epsilon;
  324. /*converged = converged || (iterations>1 && new_stress>old_stress); */
  325. /* in first iteration we allowed stress increase, which
  326. * might result ny imposing constraints
  327. */
  328. old_stress = new_stress;
  329. /* in determining non-overlap constraints we gradually scale up the
  330. * size of nodes to avoid local minima
  331. */
  332. if ((iterations >= maxi - 1 || converged) && opt->noverlap == 1
  333. && nsizeScale < 0.999) {
  334. nsizeScale += 0.1;
  335. if (Verbose)
  336. fprintf(stderr, "nsizescale=%f,iterations=%d\n",
  337. nsizeScale, iterations);
  338. iterations = 0;
  339. converged = false;
  340. }
  341. /* now we find the optimizer of trace(X'LX)+X'B by solving 'dim'
  342. * system of equations, thereby obtaining the new coordinates.
  343. * If we use the constraints (given by the var's: 'ordering',
  344. * 'levels' and 'num_levels'), we cannot optimize
  345. * trace(X'LX)+X'B by simply solving equations, but we have
  346. * to use a quadratic programming solver
  347. * note: 'lap2' is a packed symmetric matrix, that is its
  348. * upper-triangular part is arranged in a vector row-wise
  349. * also note: 'lap2' is really the negated laplacian (the
  350. * laplacian is -'lap2')
  351. */
  352. if (opt->noverlap == 1 && nsizeScale > 0.001) {
  353. generateNonoverlapConstraints(cMajEnvHor, nsizeScale, coords,
  354. 0,
  355. nsizeScale >= 0.5,
  356. opt);
  357. }
  358. if (cMajEnvHor->m > 0) {
  359. constrained_majorization_vpsc(cMajEnvHor, b[0], coords[0],
  360. localConstrMajorIterations);
  361. } else {
  362. /* if there are no constraints then use conjugate gradient
  363. * optimisation which should be considerably faster
  364. */
  365. if (conjugate_gradient_mkernel(lap2, coords[0], b[0], n,
  366. tolerance_cg, n) < 0) {
  367. iterations = -1;
  368. goto finish;
  369. }
  370. }
  371. if (opt->noverlap == 1 && nsizeScale > 0.001) {
  372. generateNonoverlapConstraints(cMajEnvVrt, nsizeScale, coords,
  373. 1, false, opt);
  374. }
  375. if (cMajEnvVrt->m > 0) {
  376. if (constrained_majorization_vpsc(cMajEnvVrt, b[1], coords[1],
  377. localConstrMajorIterations) < 0) {
  378. iterations = -1;
  379. goto finish;
  380. }
  381. } else {
  382. conjugate_gradient_mkernel(lap2, coords[1], b[1], n,
  383. tolerance_cg, n);
  384. }
  385. }
  386. if (Verbose) {
  387. fprintf(stderr, "\nfinal e = %f %d iterations %.2f sec\n",
  388. new_stress, iterations, elapsed_sec());
  389. }
  390. deleteCMajEnvVPSC(cMajEnvHor);
  391. deleteCMajEnvVPSC(cMajEnvVrt);
  392. if (opt->noverlap == 2) {
  393. /* fprintf(stderr, "Removing overlaps as post-process...\n"); */
  394. removeoverlaps(orig_n, coords, opt);
  395. }
  396. finish:
  397. if (coords != NULL) {
  398. for (i = 0; i < dim; i++) {
  399. for (int j = 0; j < orig_n; j++) {
  400. d_coords[i][j] = coords[i][j];
  401. }
  402. }
  403. free(coords[0]);
  404. free(coords);
  405. }
  406. if (b) {
  407. free(b[0]);
  408. free(b);
  409. }
  410. free(tmp_coords);
  411. free(dist_accumulator);
  412. free(degrees);
  413. free(lap2);
  414. free(lap1);
  415. return iterations;
  416. }
  417. #endif /* IPSEPCOLA */