edge_bundling.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618
  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 <sparse/SparseMatrix.h>
  16. #include <mingle/edge_bundling.h>
  17. #include <time.h>
  18. #include <sparse/clustering.h>
  19. #include <mingle/ink.h>
  20. #include <mingle/agglomerative_bundling.h>
  21. #include <string.h>
  22. #include <vector>
  23. #define SMALL 1.e-10
  24. static double norm(int n, const double *x) {
  25. double res = 0;
  26. int i;
  27. for (i = 0; i < n; i++) res += x[i]*x[i];
  28. return sqrt(res);
  29. }
  30. static double sqr_dist(int dim, const double *x, const double *y) {
  31. int i;
  32. double res = 0;
  33. for (i = 0; i < dim; i++) res += (x[i] - y[i])*(x[i] - y[i]);
  34. return res;
  35. }
  36. static double dist(int dim, const double *x, const double *y) {
  37. return sqrt(sqr_dist(dim,x,y));
  38. }
  39. static pedge pedge_new(int np, int dim, const double *x) {
  40. pedge e;
  41. e.npoints = np;
  42. e.dim = dim;
  43. e.len = np;
  44. e.x.assign(x, &x[dim * e.len]);
  45. e.edge_length = dist(dim, &x[0 * dim], &x[(np - 1) * dim]);
  46. e.wgt = 1;
  47. return e;
  48. }
  49. pedge pedge_wgt_new(int np, int dim, double *x, double wgt) {
  50. pedge e;
  51. e.npoints = np;
  52. e.dim = dim;
  53. e.len = np;
  54. e.x.assign(x, &x[dim * e.len]);
  55. e.edge_length = dist(dim, &x[0 * dim], &x[(np - 1) * dim]);
  56. e.wgt = wgt;
  57. e.wgts = std::vector<double>(np - 1, wgt);
  58. return e;
  59. }
  60. void pedge_delete(pedge &) { }
  61. static double edge_compatibility(const pedge &e1, const pedge &e2) {
  62. /* two edges are u1->v1, u2->v2.
  63. return 1 if two edges are exactly the same, 0 if they are very different.
  64. */
  65. double dist1, dist2, len1, len2;
  66. const int dim = e1.dim;
  67. bool flipped = false;
  68. const double *u1 = e1.x.data();
  69. const double *v1 = e1.x.data() + e1.npoints * dim - dim;
  70. const double *u2 = e2.x.data();
  71. const double *v2 = e2.x.data() + e2.npoints * dim - dim;
  72. dist1 = sqr_dist(dim, u1, u2) + sqr_dist(dim, v1, v2);
  73. dist2 = sqr_dist(dim, u1, v2) + sqr_dist(dim, v1, u2);
  74. if (dist1 > dist2){
  75. std::swap(u2, v2);
  76. dist1 = dist2;
  77. flipped = true;
  78. }
  79. len1 = dist(dim, u1, v1);
  80. len2 = dist(dim, u2, v2);
  81. dist1 = std::max(0.1, dist1 / (len1 + len2 + 0.0001 * dist1));
  82. if (flipped){
  83. return -1/dist1;
  84. } else {
  85. return 1/dist1;
  86. }
  87. }
  88. static double edge_compatibility_full(const pedge &e1, const pedge &e2) {
  89. /* two edges are u1->v1, u2->v2.
  90. return 1 if two edges are exactly the same, 0 if they are very different.
  91. This is based on Holten and van Wijk's paper
  92. */
  93. double dist1, dist2, len1, len2, len;
  94. double tmp, ca, cp, cs;
  95. int dim = e1.dim, i;
  96. bool flipped = false;
  97. const double *u1 = e1.x.data();
  98. const double *v1 = e1.x.data() + e1.npoints * dim - dim;
  99. const double *u2 = e2.x.data();
  100. const double *v2 = e2.x.data() + e2.npoints * dim - dim;
  101. dist1 = sqr_dist(dim, u1, u2) + sqr_dist(dim, v1, v2);
  102. dist2 = sqr_dist(dim, u1, v2) + sqr_dist(dim, v1, u2);
  103. if (dist1 > dist2){
  104. std::swap(u2, v2);
  105. dist1 = dist2;
  106. flipped = true;
  107. }
  108. len1 = std::max(dist(dim, u1, v1), SMALL);
  109. len2 = std::max(dist(dim, u2, v2), SMALL);
  110. len = 0.5*(len1+len2);
  111. /* angle compatibility */
  112. ca = 0;
  113. for (i = 0; i < dim; i++)
  114. ca += (v1[i]-u1[i])*(v2[i]-u2[i]);
  115. ca = fabs(ca/(len1*len2));
  116. assert(ca > -0.001);
  117. /* scale compatibility */
  118. cs = 2 / (std::max(len1, len2) / len + len / std::min(len1, len2));
  119. assert(cs > -0.001 && cs < 1.001);
  120. /* position compatibility */
  121. cp = 0;
  122. for (i = 0; i < dim; i++) {
  123. tmp = .5*(v1[i]+u1[i])-.5*(v2[i]+u2[i]);
  124. cp += tmp*tmp;
  125. }
  126. cp = sqrt(cp);
  127. cp = len/(len + cp);
  128. assert(cp > -0.001 && cp < 1.001);
  129. /* visibility compatibility */
  130. dist1 = cp*ca*cs;
  131. if (flipped){
  132. return -dist1;
  133. } else {
  134. return dist1;
  135. }
  136. }
  137. static void fprint_rgb(FILE* fp, int r, int g, int b, int alpha){
  138. fprintf(fp, "#%02x%02x%02x%02x", r, g, b, alpha);
  139. }
  140. void pedge_export_gv(FILE *fp, int ne, const std::vector<pedge> &edges) {
  141. double t;
  142. int i, j, k, kk, dim, sta, sto;
  143. double maxwgt = 0, len, len_total0;
  144. int r, g, b;
  145. double tt1[3]={0.15,0.5,0.85};
  146. double tt2[4]={0.15,0.4,0.6,0.85};
  147. double *tt;
  148. fprintf(fp,"strict graph{\n");
  149. /* points */
  150. for (i = 0; i < ne; i++){
  151. const pedge &edge = edges[i];
  152. const std::vector<double> &x = edge.x;
  153. dim = edge.dim;
  154. sta = 0;
  155. sto = edge.npoints - 1;
  156. fprintf(fp, "%d [pos=\"", i);
  157. for (k = 0; k < dim; k++) {
  158. if (k != 0) fprintf(fp, ",");
  159. fprintf(fp, "%f", x[sta*dim+k]);
  160. }
  161. fprintf(fp, "\"];\n");
  162. fprintf(fp, "%d [pos=\"", i + ne);
  163. for (k = 0; k < dim; k++) {
  164. if (k != 0) fprintf(fp, ",");
  165. fprintf(fp, "%f", x[sto*dim+k]);
  166. }
  167. fprintf(fp, "\"];\n");
  168. }
  169. /* figure out max number of bundled original edges in a pedge */
  170. for (i = 0; i < ne; i++){
  171. const pedge &edge = edges[i];
  172. if (!edge.wgts.empty()) {
  173. for (j = 0; j < edge.npoints - 1; j++) {
  174. maxwgt = std::max(maxwgt, edge.wgts[j]);
  175. }
  176. }
  177. }
  178. /* spline and colors */
  179. for (i = 0; i < ne; i++){
  180. fprintf(fp,"%d -- %d [pos=\"", i, i + ne);
  181. const pedge &edge = edges[i];
  182. const std::vector<double> &x = edge.x;
  183. dim = edge.dim;
  184. /* splines */
  185. for (j = 0; j < edge.npoints; j++) {
  186. if (j != 0) {
  187. int mm = 3;
  188. fprintf(fp," ");
  189. /* there are ninterval+1 points, add 3*ninterval+2 points, get rid of internal ninternal-1 points,
  190. make into 3*ninterval+4 points so that gviz spline rendering can work */
  191. if (j == 1 || j == edge.npoints - 1) {
  192. // every interval gets 3 points inserted except the first and last one
  193. tt = tt2;
  194. mm = 4;
  195. } else {
  196. tt = tt1;
  197. }
  198. for (kk = 1; kk <= mm; kk++){
  199. t = tt[kk-1];
  200. for (k = 0; k < dim; k++) {
  201. if (k != 0) fprintf(fp,",");
  202. fprintf(fp, "%f", (x[(j-1)*dim+k]*(1-t)+x[j*dim+k]*(t)));
  203. }
  204. fprintf(fp," ");
  205. }
  206. }
  207. if (j == 0 || j == edge.npoints - 1){
  208. for (k = 0; k < dim; k++) {
  209. if (k != 0) fprintf(fp,",");
  210. fprintf(fp, "%f", x[j*dim+k]);
  211. }
  212. }
  213. }
  214. /* colors based on how much bundling */
  215. if (!edge.wgts.empty()) {
  216. fprintf(fp, "\", wgts=\"");
  217. for (j = 0; j < edge.npoints - 1; j++){
  218. if (j != 0) fprintf(fp,",");
  219. fprintf(fp, "%f", edge.wgts[j]);
  220. }
  221. len_total0 = 0;
  222. fprintf(fp, "\", color=\"");
  223. for (j = 0; j < edge.npoints - 1; j++){
  224. len = 0;
  225. for (k = 0; k < dim; k++){
  226. len += (edge.x[dim * j + k] - edge.x[dim * (j + 1) + k]) * (edge.x[dim * j + k] - edge.x[dim * (j + 1) + k]);
  227. }
  228. len = sqrt(len/k);
  229. len_total0 += len;
  230. }
  231. for (j = 0; j < edge.npoints - 1; j++) {
  232. len = 0;
  233. for (k = 0; k < dim; k++){
  234. len += (edge.x[dim * j + k] - edge.x[dim * (j + 1) + k]) * (edge.x[dim * j + k] - edge.x[dim * (j + 1) + k]);
  235. }
  236. len = sqrt(len/k);
  237. t = edge.wgts[j] / maxwgt;
  238. // interpolate between red (t = 1) to blue (t = 0)
  239. r = 255*t; g = 0; b = 255*(1-t); b = 255*(1-t);
  240. if (j != 0) fprintf(fp,":");
  241. fprint_rgb(fp, r, g, b, 85);
  242. if (j < edge.npoints - 2) fprintf(fp, ";%f", len / len_total0);
  243. }
  244. }
  245. fprintf(fp, "\"];\n");
  246. }
  247. fprintf(fp,"}\n");
  248. }
  249. #ifdef DEBUG
  250. static void pedge_print(char *comments, const pedge &e) {
  251. int i, j, dim;
  252. dim = e.dim;
  253. fprintf(stderr,"%s", comments);
  254. for (i = 0; i < e.npoints; i++){
  255. if (i > 0) fprintf(stderr,",");
  256. fprintf(stderr,"{");
  257. for (j = 0; j < dim; j++){
  258. if (j > 0) fprintf(stderr,",");
  259. fprintf(stderr, "%f", e.x[dim * i + j]);
  260. }
  261. fprintf(stderr,"}");
  262. }
  263. fprintf(stderr,"\n");
  264. }
  265. #endif
  266. void pedge_wgts_realloc(pedge &e, int n) {
  267. int i;
  268. if (n <= e.npoints)
  269. return;
  270. e.x.resize(e.dim * n, 0);
  271. if (e.wgts.empty()) {
  272. e.wgts.resize(n - 1);
  273. for (i = 0; i < e.npoints; i++)
  274. e.wgts[i] = e.wgt;
  275. } else {
  276. e.wgts.resize(n - 1);
  277. }
  278. e.len = n;
  279. }
  280. void pedge_double(pedge &e) {
  281. /* double the number of points (more precisely, add a point between two points in the polyline */
  282. int npoints = e.npoints, len = e.len, i, dim = e.dim;
  283. int j, ii, ii2, np;
  284. assert(npoints >= 2);
  285. if (npoints*2-1 > len){
  286. len = 3*npoints;
  287. e.x.resize(dim * len, 0);
  288. }
  289. std::vector<double> &x = e.x;
  290. for (i = npoints - 1; i >= 0; i--){
  291. ii = 2*i;
  292. for (j = 0; j < dim; j++){
  293. x[dim*ii + j] = x[dim*i + j];
  294. }
  295. }
  296. for (i = 0; i < npoints - 1; i++){
  297. ii = 2*i;/* left and right interpolant of a new point */
  298. ii2 = 2*(i+1);
  299. for (j = 0; j < dim; j++){
  300. x[dim*(2*i + 1) + j] = 0.5*(x[dim*ii + j] + x[dim*ii2 + j]);
  301. }
  302. }
  303. e.len = len;
  304. np = e.npoints = 2 * e.npoints - 1;
  305. e.edge_length = dist(dim, &x.data()[0 * dim], &x.data()[(np - 1) * dim]);
  306. }
  307. static void edge_tension_force(std::vector<double> &force, const pedge &e) {
  308. const std::vector<double> &x = e.x;
  309. const int dim = e.dim;
  310. const int np = e.npoints;
  311. int i, left, right, j;
  312. double s;
  313. /* tension force = ((np-1)*||2x-xleft-xright||)/||e||, so the force is nominal and unitless
  314. */
  315. s = (np - 1) / std::max(SMALL, e.edge_length);
  316. for (i = 1; i <= np - 2; i++){
  317. left = i - 1;
  318. right = i + 1;
  319. for (j = 0; j < dim; j++) force[i*dim + j] += s*(x[left*dim + j] - x[i*dim + j]);
  320. for (j = 0; j < dim; j++) force[i*dim + j] += s*(x[right*dim + j] - x[i*dim + j]);
  321. }
  322. }
  323. static void edge_attraction_force(double similarity, const pedge &e1,
  324. const pedge &e2, std::vector<double> &force) {
  325. /* attractive force from x2 applied to x1 */
  326. const std::vector<double> &x1 = e1.x, &x2 = e2.x;
  327. const int dim = e1.dim;
  328. const int np = e1.npoints;
  329. int i, j;
  330. double dist, s, ss;
  331. const double edge_length = e1.edge_length;
  332. assert(e1.npoints == e2.npoints);
  333. /* attractive force = 1/d where d = D/||e1|| is the relative distance, D is the distance between e1 and e2.
  334. so the force is nominal and unitless
  335. */
  336. if (similarity > 0){
  337. s = edge_length;
  338. s = similarity*edge_length;
  339. for (i = 1; i <= np - 2; i++){
  340. dist = sqr_dist(dim, &x1.data()[i*dim], &x2.data()[i*dim]);
  341. if (dist < SMALL) dist = SMALL;
  342. ss = s/(dist+0.1*edge_length*sqrt(dist));
  343. for (j = 0; j < dim; j++) force[i*dim + j] += ss*(x2[i*dim + j] - x1[i*dim + j]);
  344. }
  345. } else {/* clip e2 */
  346. s = -edge_length;
  347. s = -similarity*edge_length;
  348. for (i = 1; i <= np - 2; i++){
  349. dist = sqr_dist(dim, &x1.data()[i*dim], &x2.data()[(np - 1 - i)*dim]);
  350. if (dist < SMALL) dist = SMALL;
  351. ss = s/(dist+0.1*edge_length*sqrt(dist));
  352. for (j = 0; j < dim; j++) force[i*dim + j] += ss*(x2[(np - 1 - i)*dim + j] - x1[i*dim + j]);
  353. }
  354. }
  355. }
  356. static void force_directed_edge_bundling(SparseMatrix A,
  357. std::vector<pedge> &edges, int maxit,
  358. double step0, double K) {
  359. int i, j, ne = A->n, k;
  360. int *ia = A->ia, *ja = A->ja, iter = 0;
  361. double *a = (double*) A->a;
  362. const int np = edges[0].npoints, dim = edges[0].dim;
  363. double step = step0;
  364. double fnorm_a, fnorm_t, edge_length, start;
  365. if (Verbose > 1)
  366. fprintf(stderr, "total interaction pairs = %d out of %d, avg neighbors per edge = %f\n",A->nz, A->m*A->m, A->nz/(double) A->m);
  367. std::vector<double> force_t(dim * np);
  368. std::vector<double> force_a(dim * np);
  369. while (step > 0.001 && iter < maxit){
  370. start = clock();
  371. iter++;
  372. for (i = 0; i < ne; i++){
  373. for (j = 0; j < dim*np; j++) {
  374. force_t[j] = 0.;
  375. force_a[j] = 0.;
  376. }
  377. pedge &e1 = edges[i];
  378. std::vector<double> &x = e1.x;
  379. edge_tension_force(force_t, e1);
  380. for (j = ia[i]; j < ia[i+1]; j++){
  381. const pedge &e2 = edges[ja[j]];
  382. edge_attraction_force(a[j], e1, e2, force_a);
  383. }
  384. fnorm_t = std::max(SMALL, norm(dim * (np - 2), &force_t.data()[dim]));
  385. fnorm_a = std::max(SMALL, norm(dim * (np - 2), &force_a.data()[dim]));
  386. edge_length = e1.edge_length;
  387. for (j = 1; j <= np - 2; j++){
  388. for (k = 0; k < dim; k++) {
  389. x[j * dim + k] += step * edge_length
  390. * (force_t[j * dim + k] + K * force_a[j * dim+k])
  391. / hypot(fnorm_t, K * fnorm_a);
  392. }
  393. }
  394. }
  395. step = step*0.9;
  396. if (Verbose > 1)
  397. fprintf(stderr, "iter ==== %d cpu = %f npoints = %d\n",iter, ((double) (clock() - start))/CLOCKS_PER_SEC, np - 2);
  398. }
  399. }
  400. static void modularity_ink_bundling(int dim, int ne, SparseMatrix B,
  401. std::vector<pedge> &edges,
  402. double angle_param, double angle) {
  403. int *assignment = NULL, nclusters;
  404. double modularity;
  405. int *clusterp, *clusters;
  406. SparseMatrix D, C;
  407. point_t meet1, meet2;
  408. double ink0, ink1;
  409. int i, j, jj;
  410. SparseMatrix BB;
  411. /* B may contain negative entries */
  412. BB = SparseMatrix_copy(B);
  413. BB = SparseMatrix_apply_fun(BB, fabs);
  414. modularity_clustering(BB, true, 0, &nclusters, &assignment, &modularity);
  415. SparseMatrix_delete(BB);
  416. if (Verbose > 1) fprintf(stderr, "there are %d clusters, modularity = %f\n",nclusters, modularity);
  417. C = SparseMatrix_new(1, 1, 1, MATRIX_TYPE_PATTERN, FORMAT_COORD);
  418. for (i = 0; i < ne; i++){
  419. jj = assignment[i];
  420. SparseMatrix_coordinate_form_add_entry(C, jj, i, NULL);
  421. }
  422. D = SparseMatrix_from_coordinate_format(C);
  423. SparseMatrix_delete(C);
  424. clusterp = D->ia;
  425. clusters = D->ja;
  426. for (i = 0; i < nclusters; i++) {
  427. ink1 = ink(edges, clusterp[i + 1] - clusterp[i], &clusters[clusterp[i]],
  428. &ink0, &meet1, &meet2, angle_param, angle);
  429. if (Verbose > 1)
  430. fprintf(stderr,"nedges = %d ink0 = %f, ink1 = %f\n",clusterp[i+1] - clusterp[i], ink0, ink1);
  431. if (ink1 < ink0){
  432. for (j = clusterp[i]; j < clusterp[i+1]; j++){
  433. /* make this edge 5 points, insert two meeting points at 1 and 2, make 3 the last point */
  434. pedge_double(edges[clusters[j]]);
  435. pedge_double(edges[clusters[j]]);
  436. pedge &e = edges[clusters[j]];
  437. e.x[1 * dim] = meet1.x;
  438. e.x[1 * dim + 1] = meet1.y;
  439. e.x[2 * dim] = meet2.x;
  440. e.x[2 * dim + 1] = meet2.y;
  441. e.x[3 * dim] = e.x[4 * dim];
  442. e.x[3 * dim + 1] = e.x[4 * dim + 1];
  443. e.npoints = 4;
  444. }
  445. }
  446. }
  447. SparseMatrix_delete(D);
  448. }
  449. static SparseMatrix check_compatibility(SparseMatrix A, int ne,
  450. const std::vector<pedge> &edges,
  451. int compatibility_method, double tol) {
  452. /* go through the links and make sure edges are compatible */
  453. SparseMatrix B, C;
  454. int *ia, *ja, i, j, jj;
  455. double start;
  456. double dist;
  457. B = SparseMatrix_new(1, 1, 1, MATRIX_TYPE_REAL, FORMAT_COORD);
  458. ia = A->ia; ja = A->ja;
  459. start = clock();
  460. for (i = 0; i < ne; i++){
  461. for (j = ia[i]; j < ia[i+1]; j++){
  462. jj = ja[j];
  463. if (i == jj) continue;
  464. if (compatibility_method == COMPATIBILITY_DIST){
  465. dist = edge_compatibility_full(edges[i], edges[jj]);
  466. } else if (compatibility_method == COMPATIBILITY_FULL){
  467. dist = edge_compatibility(edges[i], edges[jj]);
  468. }
  469. if (fabs(dist) > tol){
  470. B = SparseMatrix_coordinate_form_add_entry(B, i, jj, &dist);
  471. B = SparseMatrix_coordinate_form_add_entry(B, jj, i, &dist);
  472. }
  473. }
  474. }
  475. C = SparseMatrix_from_coordinate_format(B);
  476. SparseMatrix_delete(B);
  477. B = C;
  478. if (Verbose > 1)
  479. fprintf(stderr, "edge compatibilitu time = %f\n",((double) (clock() - start))/CLOCKS_PER_SEC);
  480. return B;
  481. }
  482. std::vector<pedge> edge_bundling(SparseMatrix A0, int dim,
  483. const std::vector<double> &x, int maxit_outer,
  484. double K, int method, int nneighbor,
  485. int compatibility_method, int max_recursion,
  486. double angle_param, double angle) {
  487. /* bundle edges.
  488. A: edge graph
  489. x: edge i is at {p,q},
  490. . where p = x[2*dim*i : 2*dim*i+dim-1]
  491. . and q = x[2*dim*i+dim : 2*dim*i+2*dim-1]
  492. maxit_outer: max outer iteration for force directed bundling. Every outer iter subdivide each edge segment into 2.
  493. K: nominal edge length in force directed bundling
  494. method: which method to use.
  495. nneighbor: number of neighbors to be used in forming nearest neighbor graph. Used only in agglomerative method
  496. compatibility_method: which method to use to calculate compatibility. Used only in force directed.
  497. max_recursion: used only in agglomerative method. Specify how many level of recursion to do to bundle bundled edges again
  498. */
  499. int ne = A0->m;
  500. SparseMatrix A = A0, B = NULL;
  501. int i;
  502. double tol = 0.001;
  503. int k;
  504. double step0 = 0.1, start = 0.0;
  505. int maxit = 10;
  506. assert(A->n == ne);
  507. std::vector<pedge> edges;
  508. edges.reserve(ne);
  509. for (i = 0; i < ne; i++){
  510. edges.emplace_back(pedge_new(2, dim, &x.data()[dim * 2 * i]));
  511. }
  512. A = SparseMatrix_symmetrize(A0, true);
  513. if (Verbose) start = clock();
  514. if (method == METHOD_INK){
  515. /* go through the links and make sure edges are compatible */
  516. B = check_compatibility(A, ne, edges, compatibility_method, tol);
  517. modularity_ink_bundling(dim, ne, B, edges, angle_param, angle);
  518. } else if (method == METHOD_INK_AGGLOMERATE){
  519. /* plan: merge a node with its neighbors if doing so improve. Form coarsening graph, repeat until no more ink saving */
  520. agglomerative_ink_bundling(dim, A, edges, nneighbor, max_recursion,
  521. angle_param, angle);
  522. } else if (method == METHOD_FD){/* FD method */
  523. /* go through the links and make sure edges are compatible */
  524. B = check_compatibility(A, ne, edges, compatibility_method, tol);
  525. for (k = 0; k < maxit_outer; k++){
  526. for (i = 0; i < ne; i++){
  527. pedge_double(edges[i]);
  528. }
  529. step0 /= 2;
  530. force_directed_edge_bundling(B, edges, maxit, step0, K);
  531. }
  532. } else if (method == METHOD_NONE){
  533. ;
  534. } else {
  535. assert(0);
  536. }
  537. if (Verbose)
  538. fprintf(stderr, "total edge bundling cpu = %f\n",((double) (clock() - start))/CLOCKS_PER_SEC);
  539. if (B != A) SparseMatrix_delete(B);
  540. if (A != A0) SparseMatrix_delete(A);
  541. return edges;
  542. }