constrained_majorization.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. /*************************************************************************
  2. * Copyright (c) 2011 AT&T Intellectual Property
  3. * All rights reserved. This program and the accompanying materials
  4. * are made available under the terms of the Eclipse Public License v1.0
  5. * which accompanies this distribution, and is available at
  6. * https://www.eclipse.org/legal/epl-v10.html
  7. *
  8. * Contributors: Details at https://graphviz.org
  9. *************************************************************************/
  10. #include <neatogen/digcola.h>
  11. #include <util/alloc.h>
  12. #ifdef DIGCOLA
  13. #include <math.h>
  14. #include <stdbool.h>
  15. #include <stdlib.h>
  16. #include <time.h>
  17. #include <stdio.h>
  18. #include <float.h>
  19. #include <neatogen/stress.h>
  20. #include <neatogen/dijkstra.h>
  21. #include <neatogen/bfs.h>
  22. #include <neatogen/matrix_ops.h>
  23. #include <neatogen/kkutils.h>
  24. #include <neatogen/conjgrad.h>
  25. #include <neatogen/quad_prog_solver.h>
  26. #include <neatogen/matrix_ops.h>
  27. #define localConstrMajorIterations 15
  28. #define levels_sep_tol 1e-1
  29. int stress_majorization_with_hierarchy(vtx_data * graph, /* Input graph in sparse representation */
  30. int n, /* Number of nodes */
  31. double **d_coords, /* Coordinates of nodes (output layout) */
  32. node_t ** nodes, /* Original nodes */
  33. int dim, /* Dimemsionality of layout */
  34. int opts, /* options */
  35. int model, /* difference model */
  36. int maxi, /* max iterations */
  37. double levels_gap)
  38. {
  39. int iterations = 0; /* Output: number of iteration of the process */
  40. /*************************************************
  41. ** Computation of full, dense, unrestricted k-D **
  42. ** stress minimization by majorization **
  43. ** This function imposes HIERARCHY CONSTRAINTS **
  44. *************************************************/
  45. int i, k;
  46. bool directionalityExist = false;
  47. float *lap1 = NULL;
  48. float *dist_accumulator = NULL;
  49. float *tmp_coords = NULL;
  50. float **b = NULL;
  51. double *degrees = NULL;
  52. float *lap2 = NULL;
  53. int lap_length;
  54. float *f_storage = NULL;
  55. float **coords = NULL;
  56. double conj_tol = tolerance_cg; /* tolerance of Conjugate Gradient */
  57. CMajEnv *cMajEnv = NULL;
  58. double y_0;
  59. int length;
  60. int smart_ini = opts & opt_smart_init;
  61. DistType diameter;
  62. float *Dij = NULL;
  63. /* to compensate noises, we never consider gaps smaller than 'abs_tol' */
  64. double abs_tol = 1e-2;
  65. /* Additionally, we never consider gaps smaller than 'abs_tol'*<avg_gap> */
  66. double relative_tol = levels_sep_tol;
  67. int *ordering = NULL, *levels = NULL;
  68. float constant_term;
  69. double degree;
  70. int step;
  71. float val;
  72. double old_stress, new_stress;
  73. bool converged;
  74. int len;
  75. int num_levels;
  76. if (graph[0].edists != NULL) {
  77. for (i = 0; i < n; i++) {
  78. for (size_t j = 1; j < graph[i].nedges; j++) {
  79. directionalityExist |= graph[i].edists[j] != 0;
  80. }
  81. }
  82. }
  83. if (!directionalityExist) {
  84. return stress_majorization_kD_mkernel(graph, n,
  85. d_coords, nodes, dim, opts,
  86. model, maxi);
  87. }
  88. /******************************************************************
  89. ** First, partition nodes into layers: These are our constraints **
  90. ******************************************************************/
  91. if (smart_ini) {
  92. double *x;
  93. double *y;
  94. if (dim > 2) {
  95. /* the dim==2 case is handled below */
  96. if (stress_majorization_kD_mkernel(graph, n,
  97. d_coords + 1, nodes, dim - 1,
  98. opts, model, 15) < 0)
  99. return -1;
  100. /* now copy the y-axis into the (dim-1)-axis */
  101. for (i = 0; i < n; i++) {
  102. d_coords[dim - 1][i] = d_coords[1][i];
  103. }
  104. }
  105. x = d_coords[0];
  106. y = d_coords[1];
  107. if (compute_y_coords(graph, n, y, n)) {
  108. iterations = -1;
  109. goto finish;
  110. }
  111. if (compute_hierarchy(graph, n, abs_tol, relative_tol, y, &ordering,
  112. &levels, &num_levels)) {
  113. iterations = -1;
  114. goto finish;
  115. }
  116. if (num_levels < 1) {
  117. /* no hierarchy found, use faster algorithm */
  118. free(levels);
  119. return stress_majorization_kD_mkernel(graph, n,
  120. d_coords, nodes, dim,
  121. opts, model, maxi);
  122. }
  123. if (levels_gap > 0) {
  124. /* ensure that levels are separated in the initial layout */
  125. double displacement = 0;
  126. int stop;
  127. for (i = 0; i < num_levels; i++) {
  128. displacement +=
  129. MAX(0.0,
  130. levels_gap - (y[ordering[levels[i]]] +
  131. displacement -
  132. y[ordering[levels[i] - 1]]));
  133. stop = i < num_levels - 1 ? levels[i + 1] : n;
  134. for (int j = levels[i]; j < stop; j++) {
  135. y[ordering[j]] += displacement;
  136. }
  137. }
  138. }
  139. if (dim == 2) {
  140. if (IMDS_given_dim(graph, n, y, x, Epsilon)) {
  141. iterations = -1;
  142. goto finish;
  143. }
  144. }
  145. } else {
  146. initLayout(n, dim, d_coords, nodes);
  147. if (compute_hierarchy(graph, n, abs_tol, relative_tol, NULL, &ordering,
  148. &levels, &num_levels)) {
  149. iterations = -1;
  150. goto finish;
  151. }
  152. }
  153. if (n == 1) {
  154. free(levels);
  155. return 0;
  156. }
  157. /****************************************************
  158. ** Compute the all-pairs-shortest-distances matrix **
  159. ****************************************************/
  160. if (maxi == 0) {
  161. free(levels);
  162. return iterations;
  163. }
  164. if (Verbose)
  165. start_timer();
  166. if (model == MODEL_SUBSET) {
  167. /* weight graph to separate high-degree nodes */
  168. /* and perform slower Dijkstra-based computation */
  169. if (Verbose)
  170. fprintf(stderr, "Calculating subset model");
  171. Dij = compute_apsp_artificial_weights_packed(graph, n);
  172. } else if (model == MODEL_CIRCUIT) {
  173. Dij = circuitModel(graph, n);
  174. if (!Dij) {
  175. agwarningf(
  176. "graph is disconnected. Hence, the circuit model\n");
  177. agerr(AGPREV,
  178. "is undefined. Reverting to the shortest path model.\n");
  179. }
  180. } else if (model == MODEL_MDS) {
  181. if (Verbose)
  182. fprintf(stderr, "Calculating MDS model");
  183. Dij = mdsModel(graph, n);
  184. }
  185. if (!Dij) {
  186. if (Verbose)
  187. fprintf(stderr, "Calculating shortest paths");
  188. Dij = compute_apsp_packed(graph, n);
  189. }
  190. if (Verbose) {
  191. fprintf(stderr, ": %.2f sec\n", elapsed_sec());
  192. fprintf(stderr, "Setting initial positions");
  193. start_timer();
  194. }
  195. diameter = -1;
  196. length = n + n * (n - 1) / 2;
  197. for (i = 0; i < length; i++) {
  198. if (Dij[i] > diameter) {
  199. diameter = (int) Dij[i];
  200. }
  201. }
  202. if (!smart_ini) {
  203. /* for numerical stability, scale down layout */
  204. /* No Jiggling, might conflict with constraints */
  205. double max = 1;
  206. for (i = 0; i < dim; i++) {
  207. for (int j = 0; j < n; j++) {
  208. max = fmax(max, fabs(d_coords[i][j]));
  209. }
  210. }
  211. for (i = 0; i < dim; i++) {
  212. for (int j = 0; j < n; j++) {
  213. d_coords[i][j] *= 10 / max;
  214. }
  215. }
  216. }
  217. if (levels_gap > 0) {
  218. double sum1, sum2, scale_ratio;
  219. int count;
  220. sum1 = (float) (n * (n - 1) / 2);
  221. sum2 = 0;
  222. for (count = 0, i = 0; i < n - 1; i++) {
  223. count++; // skip self distance
  224. for (int j = i + 1; j < n; j++, count++) {
  225. sum2 += distance_kD(d_coords, dim, i, j) / Dij[count];
  226. }
  227. }
  228. scale_ratio = sum2 / sum1;
  229. /* double scale_ratio=10; */
  230. for (i = 0; i < length; i++) {
  231. Dij[i] *= (float) scale_ratio;
  232. }
  233. }
  234. /**************************
  235. ** Layout initialization **
  236. **************************/
  237. for (i = 0; i < dim; i++) {
  238. orthog1(n, d_coords[i]);
  239. }
  240. /* for the y-coords, don't center them, but translate them so y[0]=0 */
  241. y_0 = d_coords[1][0];
  242. for (i = 0; i < n; i++) {
  243. d_coords[1][i] -= y_0;
  244. }
  245. coords = gv_calloc(dim, sizeof(float *));
  246. f_storage = gv_calloc(dim * n, sizeof(float));
  247. for (i = 0; i < dim; i++) {
  248. coords[i] = f_storage + i * n;
  249. for (int j = 0; j < n; j++) {
  250. coords[i][j] = (float)d_coords[i][j];
  251. }
  252. }
  253. /* compute constant term in stress sum
  254. * which is \sum_{i<j} w_{ij}d_{ij}^2
  255. */
  256. constant_term = (float) (n * (n - 1) / 2);
  257. if (Verbose)
  258. fprintf(stderr, ": %.2f sec", elapsed_sec());
  259. /**************************
  260. ** Laplacian computation **
  261. **************************/
  262. lap2 = Dij;
  263. lap_length = n + n * (n - 1) / 2;
  264. square_vec(lap_length, lap2);
  265. /* compute off-diagonal entries */
  266. invert_vec(lap_length, lap2);
  267. /* compute diagonal entries */
  268. int count = 0;
  269. degrees = gv_calloc(n, sizeof(double));
  270. set_vector_val(n, 0, degrees);
  271. for (i = 0; i < n - 1; i++) {
  272. degree = 0;
  273. count++; // skip main diag entry
  274. for (int j = 1; j < n - i; j++, count++) {
  275. val = lap2[count];
  276. degree += val;
  277. degrees[i + j] -= val;
  278. }
  279. degrees[i] -= degree;
  280. }
  281. for (step = n, count = 0, i = 0; i < n; i++, count += step, step--) {
  282. lap2[count] = (float) degrees[i];
  283. }
  284. /*************************
  285. ** Layout optimization **
  286. *************************/
  287. b = gv_calloc(dim, sizeof(float *));
  288. b[0] = gv_calloc(dim * n, sizeof(float));
  289. for (k = 1; k < dim; k++) {
  290. b[k] = b[0] + k * n;
  291. }
  292. tmp_coords = gv_calloc(n, sizeof(float));
  293. dist_accumulator = gv_calloc(n, sizeof(float));
  294. lap1 = gv_calloc(lap_length, sizeof(float));
  295. old_stress = DBL_MAX; /* at least one iteration */
  296. cMajEnv =
  297. initConstrainedMajorization(lap2, n, ordering, levels, num_levels);
  298. for (converged = false, iterations = 0;
  299. iterations < maxi && !converged; iterations++) {
  300. /* First, construct Laplacian of 1/(d_ij*|p_i-p_j|) */
  301. set_vector_val(n, 0, degrees);
  302. sqrt_vecf(lap_length, lap2, lap1);
  303. for (count = 0, i = 0; i < n - 1; i++) {
  304. len = n - i - 1;
  305. /* init 'dist_accumulator' with zeros */
  306. set_vector_valf(n, 0, dist_accumulator);
  307. /* put into 'dist_accumulator' all squared distances
  308. * between 'i' and 'i'+1,...,'n'-1
  309. */
  310. for (k = 0; k < dim; k++) {
  311. set_vector_valf(len, coords[k][i], tmp_coords);
  312. vectors_mult_additionf(len, tmp_coords, -1, coords[k] + i + 1);
  313. square_vec(len, tmp_coords);
  314. vectors_additionf(len, tmp_coords, dist_accumulator, dist_accumulator);
  315. }
  316. /* convert to 1/d_{ij} */
  317. invert_sqrt_vec(len, dist_accumulator);
  318. /* detect overflows */
  319. for (int j = 0; j < len; j++) {
  320. if (dist_accumulator[j] >= FLT_MAX || dist_accumulator[j] < 0) {
  321. dist_accumulator[j] = 0;
  322. }
  323. }
  324. count++; /* save place for the main diagonal entry */
  325. degree = 0;
  326. for (int j = 0; j < len; j++, count++) {
  327. val = lap1[count] *= dist_accumulator[j];
  328. degree += val;
  329. degrees[i + j + 1] -= val;
  330. }
  331. degrees[i] -= degree;
  332. }
  333. for (step = n, count = 0, i = 0; i < n; i++, count += step, step--) {
  334. lap1[count] = (float) degrees[i];
  335. }
  336. /* Now compute b[] (L^(X(t))*X(t)) */
  337. for (k = 0; k < dim; k++) {
  338. /* b[k] := lap1*coords[k] */
  339. right_mult_with_vector_ff(lap1, n, coords[k], b[k]);
  340. }
  341. /* compute new stress
  342. * remember that the Laplacians are negated, so we subtract
  343. * instead of add and vice versa
  344. */
  345. new_stress = 0;
  346. for (k = 0; k < dim; k++) {
  347. new_stress += vectors_inner_productf(n, coords[k], b[k]);
  348. }
  349. new_stress *= 2;
  350. new_stress += constant_term; // only after mult by 2
  351. for (k = 0; k < dim; k++) {
  352. right_mult_with_vector_ff(lap2, n, coords[k], tmp_coords);
  353. new_stress -= vectors_inner_productf(n, coords[k], tmp_coords);
  354. }
  355. /* check for convergence */
  356. converged =
  357. fabs(new_stress - old_stress) / fabs(old_stress + 1e-10) <
  358. Epsilon;
  359. converged |= iterations > 1 && new_stress > old_stress;
  360. /* in first iteration we allowed stress increase, which
  361. * might result ny imposing constraints
  362. */
  363. old_stress = new_stress;
  364. for (k = 0; k < dim; k++) {
  365. /* now we find the optimizer of trace(X'LX)+X'B by solving 'dim'
  366. * system of equations, thereby obtaining the new coordinates.
  367. * If we use the constraints (given by the var's: 'ordering',
  368. * 'levels' and 'num_levels'), we cannot optimize
  369. * trace(X'LX)+X'B by simply solving equations, but we have
  370. * to use a quadratic programming solver
  371. * note: 'lap2' is a packed symmetric matrix, that is its
  372. * upper-triangular part is arranged in a vector row-wise
  373. * also note: 'lap2' is really the negated laplacian (the
  374. * laplacian is -'lap2')
  375. */
  376. if (k == 1) {
  377. /* use quad solver in the y-dimension */
  378. constrained_majorization_new_with_gaps(cMajEnv, b[k],
  379. coords, k,
  380. localConstrMajorIterations,
  381. levels_gap);
  382. } else {
  383. /* use conjugate gradient for all dimensions except y */
  384. if (conjugate_gradient_mkernel(lap2, coords[k], b[k], n,
  385. conj_tol, n)) {
  386. iterations = -1;
  387. goto finish;
  388. }
  389. }
  390. }
  391. }
  392. if (coords != NULL) {
  393. for (i = 0; i < dim; i++) {
  394. for (int j = 0; j < n; j++) {
  395. d_coords[i][j] = coords[i][j];
  396. }
  397. }
  398. free(coords[0]);
  399. free(coords);
  400. }
  401. free(tmp_coords);
  402. free(dist_accumulator);
  403. free(degrees);
  404. free(lap2);
  405. free(lap1);
  406. finish:
  407. if (cMajEnv != NULL) {
  408. deleteCMajEnv(cMajEnv);
  409. }
  410. if (b) {
  411. free(b[0]);
  412. free(b);
  413. }
  414. free(ordering);
  415. free(levels);
  416. return iterations;
  417. }
  418. #endif /* DIGCOLA */