agglomerative_bundling.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  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 <algorithm>
  11. #include <common/types.h>
  12. #include <common/globals.h>
  13. #include <sparse/general.h>
  14. #include <math.h>
  15. #include <time.h>
  16. #include <sparse/SparseMatrix.h>
  17. #include <mingle/edge_bundling.h>
  18. #include <mingle/ink.h>
  19. #include <mingle/agglomerative_bundling.h>
  20. #include <mingle/nearest_neighbor_graph.h>
  21. #include <string.h>
  22. #include <vector>
  23. enum {MINGLE_DEBUG=0};
  24. namespace {
  25. struct Agglomerative_Ink_Bundling {
  26. Agglomerative_Ink_Bundling(int level_, int n_, SparseMatrix A_,
  27. const std::vector<pedge> &edges_)
  28. : level(level_), n(n_), A(A_), edges(edges_) {}
  29. int level; /* 0, 1, ... */
  30. int n;
  31. SparseMatrix A; /* n x n matrix, where n is the number of edges/bundles in
  32. this level */
  33. SparseMatrix R0 = nullptr; /* this is basically R[level - 1].R[level - 2]...R[0], which
  34. gives the map of bundling i to the original edges: first
  35. row of R0 gives the nodes on the finest grid corresponding
  36. to the coarsest node 1, etc */
  37. SparseMatrix R = nullptr; ///< striction mtrix from level to level + 1
  38. std::vector<double>
  39. inks; /* amount of ink needed to draw this edge/bundle. Dimension n. */
  40. double total_ink = -1; /* amount of ink needed to draw this edge/bundle. Dimension
  41. n. */
  42. std::vector<pedge>
  43. edges; /* the original edge info. This does not vary level to level and
  44. is of dimenion n0, where n0 is the number of original edges */
  45. bool delete_top_level_A = false; /*whether the top level matrix should be deleted on
  46. garbage collecting the grid */
  47. };
  48. } // namespace
  49. using aib_t = std::vector<Agglomerative_Ink_Bundling>;
  50. static aib_t Agglomerative_Ink_Bundling_init(SparseMatrix A,
  51. const std::vector<pedge> &edges,
  52. int level) {
  53. int n = A->n, i;
  54. assert(SparseMatrix_is_symmetric(A, true));
  55. if (!A) return {};
  56. assert(A->m == n);
  57. Agglomerative_Ink_Bundling grid(level, n, A, edges);
  58. if (level == 0){
  59. double total_ink = 0;
  60. for (i = 0; i < n; i++) {
  61. grid.inks.push_back(ink1(edges[i]));
  62. total_ink += grid.inks[i];
  63. }
  64. grid.total_ink = total_ink;
  65. }
  66. return aib_t{grid};
  67. }
  68. static void Agglomerative_Ink_Bundling_delete(aib_t &grid) {
  69. for (Agglomerative_Ink_Bundling &a : grid) {
  70. if (a.A) {
  71. if (a.level == 0) {
  72. if (a.delete_top_level_A) SparseMatrix_delete(a.A);
  73. } else {
  74. SparseMatrix_delete(a.A);
  75. }
  76. }
  77. /* on level 0, R0 = NULL, on level 1, R0 = R */
  78. if (a.level > 1) SparseMatrix_delete(a.R0);
  79. SparseMatrix_delete(a.R);
  80. }
  81. }
  82. static void Agglomerative_Ink_Bundling_establish(aib_t &grid, int *pick,
  83. double angle_param,
  84. double angle) {
  85. /* pick is a work array of dimension n, with n the total number of original edges */
  86. SparseMatrix A = grid.front().A;
  87. int n = grid.front().n, level = grid.front().level, nc = 0;
  88. int *ia = A->ia, *ja = A->ja;
  89. int i, j, k, jj, jc, jmax, ni, nj, npicks;
  90. const std::vector<pedge> &edges = grid.front().edges;
  91. const std::vector<double> &inks = grid.front().inks;
  92. double inki, inkj;
  93. double gain, maxgain, minink, total_gain = 0;
  94. int *ip = NULL, *jp = NULL, ie;
  95. std::vector<std::vector<int>> cedges;/* a table listing the content of bundled edges in the coarsen grid.
  96. cedges[i] contain the list of origonal edges that make up the bundle i in the next level */
  97. double ink0, ink1, grand_total_ink = 0, grand_total_gain = 0;
  98. point_t meet1, meet2;
  99. if (Verbose > 1)
  100. fprintf(stderr, "level ===================== %d, n = %d\n",
  101. grid.front().level, n);
  102. cedges.resize(n);
  103. std::vector<double> cinks(n, 0.0);
  104. if (grid.front().level > 0) {
  105. ip = grid.front().R0->ia;
  106. jp = grid.front().R0->ja;
  107. }
  108. assert(n == A->n);
  109. std::vector<int> matching(n, UNMATCHED);
  110. for (i = 0; i < n; i++){
  111. if (matching[i] != UNMATCHED) continue;
  112. /* find the best matching in ink saving */
  113. maxgain = 0;
  114. minink = -1;
  115. jmax = -1;
  116. for (j = ia[i]; j < ia[i+1]; j++){
  117. jj = ja[j];
  118. if (jj == i) continue;
  119. /* ink saving of merging i and j */
  120. if ((jc=matching[jj]) == UNMATCHED){
  121. /* neither i nor jj are matched */
  122. inki = inks[i]; inkj = inks[jj];
  123. if (ip && jp){/* not the first level */
  124. ni = (ip[i+1] - ip[i]);/* number of edges represented by i */
  125. nj = (ip[jj+1] - ip[jj]);/* number of edges represented by jj */
  126. memcpy(pick, &(jp[ip[i]]), sizeof(int)*ni);
  127. memcpy(pick+ni, &(jp[ip[jj]]), sizeof(int)*nj);
  128. } else {/* first level */
  129. pick[0] = i; pick[1] = jj;
  130. ni = nj = 1;
  131. }
  132. if (MINGLE_DEBUG) if (Verbose) fprintf(stderr, "ink(%d)=%f, ink(%d)=%f", i, inki, jj, inkj);
  133. } else {
  134. /* j is already matched. Its content is on cedges[jc] */
  135. inki = inks[i]; inkj = cinks[jc];
  136. if (MINGLE_DEBUG) if (Verbose) fprintf(stderr, "ink(%d)=%f, ink(%d->%d)=%f", i, inki, jj, jc, inkj);
  137. if (ip) {
  138. ni = (ip[i+1] - ip[i]);/* number of edges represented by i */
  139. memcpy(pick, &(jp[ip[i]]), sizeof(int)*ni);
  140. } else {
  141. ni = 1; pick[0] = i;
  142. }
  143. nj = cedges[jc].size();
  144. npicks = ni;
  145. for (k = 0; k < nj; k++) {
  146. pick[npicks++] = cedges[jc][k];
  147. }
  148. }
  149. npicks = ni + nj;
  150. ink1 =
  151. ink(edges, npicks, pick, &ink0, &meet1, &meet2, angle_param, angle);
  152. if (MINGLE_DEBUG) {
  153. if (Verbose) {
  154. fprintf(stderr,", if merging {");
  155. for (k = 0; k < npicks; k++) fprintf(stderr,"%d,", pick[k]);
  156. fprintf(stderr,"}, ");
  157. fprintf(stderr, " ink0=%f, ink1=%f", inki+inkj, ink1);
  158. }
  159. }
  160. gain = inki + inkj - ink1;
  161. if (MINGLE_DEBUG) if (Verbose) fprintf(stderr, " gain=%f", gain);
  162. if (gain > maxgain){
  163. maxgain = gain;
  164. minink = ink1;
  165. jmax = jj;
  166. if (MINGLE_DEBUG) if (Verbose) fprintf(stderr, "maxgain=%f", maxgain);
  167. }
  168. if (MINGLE_DEBUG) if (Verbose) fprintf(stderr, "\n");
  169. }
  170. /* now merge i and jmax */
  171. if (maxgain > 0){
  172. /* a good bundling of i and another edge jmax is found */
  173. total_gain += maxgain;
  174. jc = matching[jmax];
  175. if (jc == UNMATCHED){/* i and j both unmatched. Add j in the table first */
  176. if (MINGLE_DEBUG) if (Verbose) printf("maxgain=%f, merge %d with best edge: %d to form coarsen edge %d. Ink=%f\n",maxgain, i, jmax, nc, minink);
  177. matching[i] = matching[jmax] = nc;
  178. if (ip){
  179. for (k = ip[jmax]; k < ip[jmax+1]; k++) {
  180. ie = jp[k];
  181. cedges[nc].push_back(ie);
  182. }
  183. } else {
  184. cedges[nc].push_back(jmax);
  185. }
  186. jc = nc;
  187. nc++;
  188. } else {/*j is already matched */
  189. if (MINGLE_DEBUG) if (Verbose) printf("maxgain=%f, merge %d with existing cluster %d\n",maxgain, i, jc);
  190. matching[i] = jc;
  191. grand_total_ink -= cinks[jc];/* ink of cluster jc was already added, and will be added again as part of a larger cluster with i, so dicount */
  192. }
  193. } else {/*i can not match/bundle successfully */
  194. if (MINGLE_DEBUG) if (Verbose) fprintf(stderr, "no gain in bundling node %d\n",i);
  195. assert(maxgain <= 0);
  196. matching[i] = nc;
  197. jc = nc;
  198. minink = inks[i];
  199. nc++;
  200. }
  201. /* add i to the appropriate table */
  202. if (ip){
  203. for (k = ip[i]; k < ip[i+1]; k++) {
  204. ie = jp[k];
  205. cedges[jc].push_back(ie);
  206. }
  207. } else {
  208. cedges[jc].push_back(i);
  209. }
  210. cinks[jc] = minink;
  211. grand_total_ink += minink;
  212. grand_total_gain += maxgain;
  213. if (MINGLE_DEBUG){
  214. if (Verbose) {
  215. fprintf(stderr," coarse edge[%d]={",jc);
  216. for (const int &cedge : cedges[jc]) {
  217. fprintf(stderr,"%d,", cedge);
  218. }
  219. fprintf(stderr,"}, grand_total_gain=%f\n",grand_total_gain);
  220. }
  221. }
  222. }
  223. if (nc >= 1 && total_gain > 0){
  224. /* now set up restriction and prolongation operator */
  225. SparseMatrix P, R, R1, R0, B, cA;
  226. double one = 1.;
  227. R1 = SparseMatrix_new(nc, n, 1, MATRIX_TYPE_REAL, FORMAT_COORD);
  228. for (i = 0; i < n; i++){
  229. jj = matching[i];
  230. SparseMatrix_coordinate_form_add_entry(R1, jj, i, &one);
  231. }
  232. R = SparseMatrix_from_coordinate_format(R1);
  233. SparseMatrix_delete(R1);
  234. P = SparseMatrix_transpose(R);
  235. B = SparseMatrix_multiply(R, A);
  236. if (!B) return;
  237. cA = SparseMatrix_multiply(B, P);
  238. if (!cA) return;
  239. SparseMatrix_delete(B);
  240. SparseMatrix_delete(P);
  241. grid.front().R = R;
  242. level++;
  243. aib_t cgrid = Agglomerative_Ink_Bundling_init(cA, edges, level);
  244. if (grid.front().R0) {
  245. R0 = SparseMatrix_multiply(R, grid.front().R0);
  246. } else {
  247. assert(grid.front().level == 0);
  248. R0 = R;
  249. }
  250. cgrid.front().R0 = R0;
  251. cgrid.front().inks = cinks;
  252. cgrid.front().total_ink = grand_total_ink;
  253. if (Verbose > 1)
  254. fprintf(stderr,
  255. "level %d->%d, edges %d -> %d, ink %f->%f , gain = %f, or %f\n",
  256. grid.front().level,
  257. cgrid.front().level,
  258. grid.front().n,
  259. cgrid.front().n,
  260. grid.front().total_ink,
  261. grand_total_ink,
  262. grid.front().total_ink - grand_total_ink,
  263. grand_total_gain);
  264. assert(fabs(grid.front().total_ink - cgrid.front().total_ink - grand_total_gain)
  265. <= 0.0001 * grid.front().total_ink);
  266. Agglomerative_Ink_Bundling_establish(cgrid, pick, angle_param, angle);
  267. grid.insert(grid.end(), cgrid.begin(), cgrid.end());
  268. } else {
  269. if (Verbose > 1) fprintf(stderr,"no more improvement, orig ink = %f, gain = %f, stop and final bundling found\n", grand_total_ink, grand_total_gain);
  270. /* no more improvement, stop and final bundling found */
  271. }
  272. }
  273. static aib_t Agglomerative_Ink_Bundling_new(SparseMatrix A0,
  274. const std::vector<pedge> &edges,
  275. double angle_param, double angle) {
  276. /* give a link of edges and their nearest neighbor graph, return a multilevel
  277. * of edge bundling based on ink saving */
  278. SparseMatrix A = A0;
  279. if (!SparseMatrix_is_symmetric(A, false) || A->type != MATRIX_TYPE_REAL){
  280. A = SparseMatrix_get_real_adjacency_matrix_symmetrized(A);
  281. }
  282. aib_t grid = Agglomerative_Ink_Bundling_init(A, edges, 0);
  283. std::vector<int> pick(A0->m);
  284. Agglomerative_Ink_Bundling_establish(grid, pick.data(), angle_param, angle);
  285. if (A != A0) grid.front().delete_top_level_A = true; // be sure to clean up later
  286. return grid;
  287. }
  288. static void agglomerative_ink_bundling_internal(
  289. int dim, SparseMatrix A, std::vector<pedge> &edges, int nneighbors,
  290. int *recurse_level, int MAX_RECURSE_LEVEL, double angle_param, double angle,
  291. double *current_ink, double *ink00) {
  292. int i, j, jj, k;
  293. int *ia, *ja;
  294. int *pick;
  295. SparseMatrix R;
  296. double ink0, ink1;
  297. point_t meet1, meet2;
  298. double wgt_all;
  299. [[maybe_unused]] const double TOL = 0.0001;
  300. clock_t start;
  301. (*recurse_level)++;
  302. if (Verbose > 1) fprintf(stderr, "agglomerative_ink_bundling_internal, recurse level ------- %d\n",*recurse_level);
  303. assert(A->m == A->n);
  304. start = clock();
  305. aib_t grid = Agglomerative_Ink_Bundling_new(A, edges, angle_param, angle);
  306. if (Verbose > 1)
  307. fprintf(stderr, "CPU for agglomerative bundling %f\n", ((double) (clock() - start))/CLOCKS_PER_SEC);
  308. ink0 = grid.front().total_ink;
  309. /* find coarsest */
  310. ink1 = grid.back().total_ink;
  311. if (*current_ink < 0){
  312. *current_ink = *ink00 = ink0;
  313. if (Verbose > 1)
  314. fprintf(stderr,"initial total ink = %f\n",*current_ink);
  315. }
  316. if (ink1 < ink0){
  317. *current_ink -= ink0 - ink1;
  318. }
  319. if (Verbose > 1)
  320. fprintf(stderr,
  321. "ink: %f->%f, edges: %d->%d, current ink = %f, percentage gain over original = %f\n",
  322. ink0,
  323. ink1,
  324. grid.front().n,
  325. grid.back().n,
  326. *current_ink,
  327. (ink0 -ink1) / (*ink00));
  328. /* if no meaningful improvement (0.0001%), out, else rebundle the middle section */
  329. if ((ink0-ink1)/(*ink00) < 0.000001 || *recurse_level > MAX_RECURSE_LEVEL) {
  330. /* project bundles up */
  331. R = grid.back().R0;
  332. if (R){
  333. ia = R->ia;
  334. ja = R->ja;
  335. for (i = 0; i < R->m; i++){
  336. pick = &(ja[ia[i]]);
  337. if (MINGLE_DEBUG) if (Verbose) fprintf(stderr,"calling ink2...\n");
  338. ink1 = ink(edges, ia[i+1]-ia[i], pick, &ink0, &meet1, &meet2, angle_param, angle);
  339. if (MINGLE_DEBUG) if (Verbose) fprintf(stderr,"finish calling ink2...\n");
  340. assert(fabs(ink1 - grid.back().inks[i]) <= std::max(TOL, TOL * ink1) && ink1 - ink0 <= TOL);
  341. assert(ink1 < 1000 * ink0); /* assert that points were found */
  342. wgt_all = 0.;
  343. if (ia[i+1]-ia[i] > 1){
  344. for (j = ia[i]; j < ia[i+1]; j++){
  345. /* make this edge 4 points, insert two meeting points at 1 and 2, make 3 the last point */
  346. jj = ja[j];
  347. pedge_double(edges[jj]);/* has to call pedge_double twice: from 2 points to 3 points to 5 points. The last point not used, may be
  348. improved later */
  349. pedge_double(edges[jj]);
  350. pedge &e = edges[jj];
  351. e.x[1 * dim] = meet1.x;
  352. e.x[1 * dim + 1] = meet1.y;
  353. e.x[2 * dim] = meet2.x;
  354. e.x[2 * dim + 1] = meet2.y;
  355. e.x[3 * dim] = e.x[4 * dim];
  356. e.x[3 * dim + 1] = e.x[4 * dim + 1];
  357. e.npoints = 4;
  358. e.wgts = std::vector<double>(4, e.wgt);
  359. wgt_all += e.wgt;
  360. }
  361. for (j = ia[i]; j < ia[i+1]; j++){
  362. pedge &e = edges[ja[j]];
  363. e.wgts[1] = wgt_all;
  364. }
  365. }
  366. }
  367. }
  368. } else {
  369. int ne, npp, l;
  370. SparseMatrix A_mid;
  371. double wgt;
  372. /* make new edges using meet1 and meet2.
  373. call Agglomerative_Ink_Bundling_new
  374. inherit new edges to old edges
  375. */
  376. R = grid.back().R0;
  377. assert(R && grid.size() > 1);/* if ink improved, we should have gone at leat 1 level down! */
  378. ia = R->ia;
  379. ja = R->ja;
  380. ne = R->m;
  381. std::vector<pedge> mid_edges(ne);
  382. std::vector<double> xx(4 * ne);
  383. for (i = 0; i < R->m; i++){
  384. pick = &(ja[ia[i]]);
  385. wgt = 0.;
  386. for (j = ia[i]; j < ia[i+1]; j++) wgt += edges[j].wgt;
  387. if (MINGLE_DEBUG) if (Verbose) fprintf(stderr,"calling ink3...\n");
  388. ink1 = ink(edges, ia[i+1]-ia[i], pick, &ink0, &meet1, &meet2, angle_param, angle);
  389. if (MINGLE_DEBUG) if (Verbose) fprintf(stderr,"done calling ink3...\n");
  390. assert(fabs(ink1 - grid.back().inks[i]) <= std::max(TOL, TOL * ink1) && ink1 - ink0 <= TOL);
  391. assert(ink1 < 1000 * ink0); /* assert that points were found */
  392. xx[i*4 + 0] = meet1.x;
  393. xx[i*4 + 1] = meet1.y;
  394. xx[i*4 + 2] = meet2.x;
  395. xx[i*4 + 3] = meet2.y;
  396. mid_edges[i] = pedge_wgt_new(2, dim, &xx.data()[i*4], wgt);
  397. }
  398. A_mid = nearest_neighbor_graph(ne, std::min(nneighbors, ne), xx);
  399. agglomerative_ink_bundling_internal(dim, A_mid, mid_edges, nneighbors, recurse_level, MAX_RECURSE_LEVEL, angle_param, angle, current_ink, ink00);
  400. SparseMatrix_delete(A_mid);
  401. /* patching edges with the new mid-section */
  402. for (i = 0; i < R->m; i++){
  403. pick = &(ja[ia[i]]);
  404. // middle section of edges that will be bundled again
  405. const pedge &midedge = mid_edges[i];
  406. npp = midedge.npoints + 2;
  407. for (j = ia[i]; j < ia[i+1]; j++){
  408. jj = ja[j];
  409. pedge_wgts_realloc(edges[jj], npp);
  410. pedge &e = edges[jj];
  411. assert(e.npoints == 2);
  412. for (l = 0; l < dim; l++){/* move the second point to the last */
  413. e.x[(npp - 1) * dim + l] = e.x[1 * dim + l];
  414. }
  415. for (k = 0; k < midedge.npoints; k++){
  416. for (l = 0; l < dim; l++){
  417. e.x[(k + 1) * dim + l] = midedge.x[k * dim + l];
  418. }
  419. if (k < midedge.npoints - 1){
  420. if (!midedge.wgts.empty()) {
  421. e.wgts[k + 1] = midedge.wgts[k];
  422. } else {
  423. e.wgts[k + 1] = midedge.wgt;
  424. }
  425. }
  426. }
  427. e.wgts[npp - 2] = e.wgts[0]; // the last interval take from the 1st interval
  428. e.npoints = npp;
  429. }
  430. }
  431. for (i = 0; i < ne; i++) pedge_delete(mid_edges[i]);
  432. }
  433. Agglomerative_Ink_Bundling_delete(grid);
  434. }
  435. void agglomerative_ink_bundling(int dim, SparseMatrix A,
  436. std::vector<pedge> &edges, int nneighbor,
  437. int MAX_RECURSE_LEVEL, double angle_param,
  438. double angle) {
  439. int recurse_level = 0;
  440. double current_ink = -1, ink0;
  441. ink_count = 0;
  442. agglomerative_ink_bundling_internal(dim, A, edges, nneighbor, &recurse_level,
  443. MAX_RECURSE_LEVEL, angle_param, angle,
  444. &current_ink, &ink0);
  445. if (Verbose > 1)
  446. fprintf(stderr,"initial total ink = %f, final total ink = %f, inksaving = %f percent, total ink_calc = %f, avg ink_calc per edge = %f\n", ink0, current_ink, (ink0-current_ink)/ink0, ink_count, ink_count/(double) A->m);
  447. }