Graphviz 16.1.1~dev.20261017.1520
Loading...
Searching...
No Matches
agglomerative_bundling.cpp
Go to the documentation of this file.
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 v2.0
5 * which accompanies this distribution, and is available at
6 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
7 *
8 * Contributors: Details at https://graphviz.org
9 *************************************************************************/
10
11#include "config.h"
12
13#include <algorithm>
14#include <cassert>
15#include <common/globals.h>
16#include <common/types.h>
17#include <math.h>
20#include <mingle/ink.h>
22#include <sparse/SparseMatrix.h>
23#include <sparse/general.h>
24#include <string.h>
25#include <time.h>
26#include <vector>
27
28enum { MINGLE_DEBUG = 0 };
29
30namespace {
31struct Agglomerative_Ink_Bundling {
32 Agglomerative_Ink_Bundling(int level_, int n_, SparseMatrix A_,
33 const std::vector<pedge> &edges_)
34 : level(level_), n(n_), A(A_), edges(edges_) {}
35
36 int level; /* 0, 1, ... */
37 int n;
38 SparseMatrix A; /* n x n matrix, where n is the number of edges/bundles in
39 this level */
40 SparseMatrix R0 = nullptr; /* this is basically R[level - 1].R[level -
41 2]...R[0], which gives the map of bundling i to the
42 original edges: first row of R0 gives the nodes on the
43 finest grid corresponding to the coarsest node 1, etc */
44 SparseMatrix R = nullptr;
45 std::vector<double>
46 inks; /* amount of ink needed to draw this edge/bundle. Dimension n. */
47 double total_ink = -1; /* amount of ink needed to draw this edge/bundle.
48 Dimension n. */
49 std::vector<pedge>
50 edges; /* the original edge info. This does not vary level to level and
51 is of dimenion n0, where n0 is the number of original edges */
52 bool delete_top_level_A = false; /*whether the top level matrix should be
53 deleted on garbage collecting the grid */
54};
55} // namespace
56
57using aib_t = std::vector<Agglomerative_Ink_Bundling>;
58
60 const std::vector<pedge> &edges,
61 int level) {
62 assert(A != nullptr);
63 int n = A->n, i;
64
65 assert(SparseMatrix_is_symmetric(A, true));
66
67 assert(A->m == (size_t)n);
68 Agglomerative_Ink_Bundling grid(level, n, A, edges);
69 if (level == 0) {
70 double total_ink = 0;
71 for (i = 0; i < n; i++) {
72 grid.inks.push_back(ink1(edges[i]));
73 total_ink += grid.inks[i];
74 }
75 grid.total_ink = total_ink;
76 }
77 return aib_t{grid};
78}
79
81 for (Agglomerative_Ink_Bundling &a : grid) {
82 if (a.A) {
83 if (a.level == 0) {
84 if (a.delete_top_level_A)
86 } else {
88 }
89 }
90 /* on level 0, R0 = NULL, on level 1, R0 = R */
91 if (a.level > 1)
94 }
95}
96
98 double angle_param,
99 double angle) {
100 /* pick is a work array of dimension n, with n the total number of original
101 * edges */
102 SparseMatrix A = grid.front().A;
103 int n = grid.front().n, level = grid.front().level, nc = 0;
104 int *ia = A->ia, *ja = A->ja;
105 int i, j, k, jj, jc, jmax, ni, nj, npicks;
106 const std::vector<pedge> &edges = grid.front().edges;
107 const std::vector<double> &inks = grid.front().inks;
108 double inki, inkj;
109 double gain, maxgain, minink, total_gain = 0;
110 int *ip = nullptr, *jp = nullptr, ie;
111 std::vector<std::vector<int>>
112 cedges; /* a table listing the content of bundled edges in the coarsen
113grid. cedges[i] contain the list of origonal edges that make up the bundle i in
114the next level */
115 double ink0, ink1, grand_total_ink = 0, grand_total_gain = 0;
116 point_t meet1, meet2;
117
118 if (Verbose > 1)
119 fprintf(stderr, "level ===================== %d, n = %d\n",
120 grid.front().level, n);
121 cedges.resize(n);
122 std::vector<double> cinks(n, 0.0);
123
124 if (grid.front().level > 0) {
125 ip = grid.front().R0->ia;
126 jp = grid.front().R0->ja;
127 }
128
129 assert(n == A->n);
130 std::vector<int> matching(n, UNMATCHED);
131
132 for (i = 0; i < n; i++) {
133 if (matching[i] != UNMATCHED)
134 continue;
135
136 /* find the best matching in ink saving */
137 maxgain = 0;
138 minink = -1;
139 jmax = -1;
140 for (j = ia[i]; j < ia[i + 1]; j++) {
141 jj = ja[j];
142 if (jj == i)
143 continue;
144
145 /* ink saving of merging i and j */
146 if ((jc = matching[jj]) == UNMATCHED) {
147 /* neither i nor jj are matched */
148 inki = inks[i];
149 inkj = inks[jj];
150 if (ip && jp) { /* not the first level */
151 ni = ip[i + 1] - ip[i]; // number of edges represented by i
152 nj = ip[jj + 1] - ip[jj]; // number of edges represented by jj
153 memcpy(pick, &jp[ip[i]], sizeof(int) * ni);
154 memcpy(pick + ni, &jp[ip[jj]], sizeof(int) * nj);
155 } else { /* first level */
156 pick[0] = i;
157 pick[1] = jj;
158 ni = nj = 1;
159 }
160 if (MINGLE_DEBUG)
161 if (Verbose)
162 fprintf(stderr, "ink(%d)=%f, ink(%d)=%f", i, inki, jj, inkj);
163 } else {
164 /* j is already matched. Its content is on cedges[jc] */
165 inki = inks[i];
166 inkj = cinks[jc];
167 if (MINGLE_DEBUG)
168 if (Verbose)
169 fprintf(stderr, "ink(%d)=%f, ink(%d->%d)=%f", i, inki, jj, jc,
170 inkj);
171 if (ip) {
172 ni = ip[i + 1] - ip[i]; // number of edges represented by i
173 memcpy(pick, &jp[ip[i]], sizeof(int) * ni);
174 } else {
175 ni = 1;
176 pick[0] = i;
177 }
178 nj = cedges[jc].size();
179 npicks = ni;
180 for (k = 0; k < nj; k++) {
181 pick[npicks++] = cedges[jc][k];
182 }
183 }
184
185 npicks = ni + nj;
186 ink1 =
187 ink(edges, npicks, pick, &ink0, &meet1, &meet2, angle_param, angle);
188 if (MINGLE_DEBUG) {
189 if (Verbose) {
190 fprintf(stderr, ", if merging {");
191 for (k = 0; k < npicks; k++)
192 fprintf(stderr, "%d,", pick[k]);
193 fprintf(stderr, "}, ");
194 fprintf(stderr, " ink0=%f, ink1=%f", inki + inkj, ink1);
195 }
196 }
197
198 gain = inki + inkj - ink1;
199 if (MINGLE_DEBUG)
200 if (Verbose)
201 fprintf(stderr, " gain=%f", gain);
202 if (gain > maxgain) {
203 maxgain = gain;
204 minink = ink1;
205 jmax = jj;
206 if (MINGLE_DEBUG)
207 if (Verbose)
208 fprintf(stderr, "maxgain=%f", maxgain);
209 }
210 if (MINGLE_DEBUG)
211 if (Verbose)
212 fprintf(stderr, "\n");
213 }
214
215 /* now merge i and jmax */
216 if (maxgain > 0) {
217 /* a good bundling of i and another edge jmax is found */
218 total_gain += maxgain;
219 jc = matching[jmax];
220 if (jc ==
221 UNMATCHED) { /* i and j both unmatched. Add j in the table first */
222 if (MINGLE_DEBUG)
223 if (Verbose)
224 printf("maxgain=%f, merge %d with best edge: %d to form coarsen "
225 "edge %d. Ink=%f\n",
226 maxgain, i, jmax, nc, minink);
227 matching[i] = matching[jmax] = nc;
228 if (ip) {
229 for (k = ip[jmax]; k < ip[jmax + 1]; k++) {
230 ie = jp[k];
231 cedges[nc].push_back(ie);
232 }
233 } else {
234 cedges[nc].push_back(jmax);
235 }
236 jc = nc;
237 nc++;
238 } else { /*j is already matched */
239 if (MINGLE_DEBUG)
240 if (Verbose)
241 printf("maxgain=%f, merge %d with existing cluster %d\n", maxgain,
242 i, jc);
243 matching[i] = jc;
244 grand_total_ink -= cinks[jc]; /* ink of cluster jc was already added,
245 and will be added again as part of a
246 larger cluster with i, so dicount */
247 }
248 } else { /*i can not match/bundle successfully */
249 if (MINGLE_DEBUG)
250 if (Verbose)
251 fprintf(stderr, "no gain in bundling node %d\n", i);
252 assert(maxgain <= 0);
253 matching[i] = nc;
254 jc = nc;
255 minink = inks[i];
256 nc++;
257 }
258
259 /* add i to the appropriate table */
260 if (ip) {
261 for (k = ip[i]; k < ip[i + 1]; k++) {
262 ie = jp[k];
263 cedges[jc].push_back(ie);
264 }
265 } else {
266 cedges[jc].push_back(i);
267 }
268 cinks[jc] = minink;
269 grand_total_ink += minink;
270 grand_total_gain += maxgain;
271
272 if (MINGLE_DEBUG) {
273 if (Verbose) {
274 fprintf(stderr, " coarse edge[%d]={", jc);
275 for (const int &cedge : cedges[jc]) {
276 fprintf(stderr, "%d,", cedge);
277 }
278 fprintf(stderr, "}, grand_total_gain=%f\n", grand_total_gain);
279 }
280 }
281 }
282
283 if (nc >= 1 && total_gain > 0) {
284 /* now set up restriction and prolongation operator */
285 SparseMatrix P, R, R1, R0, B, cA;
286 double one = 1.;
287
289 for (i = 0; i < n; i++) {
290 jj = matching[i];
292 }
297 if (!B)
298 return;
299 cA = SparseMatrix_multiply(B, P);
300 if (!cA)
301 return;
304 grid.front().R = R;
305
306 level++;
307 aib_t cgrid = Agglomerative_Ink_Bundling_init(cA, edges, level);
308
309 if (grid.front().R0) {
310 R0 = SparseMatrix_multiply(R, grid.front().R0);
311 } else {
312 assert(grid.front().level == 0);
313 R0 = R;
314 }
315 cgrid.front().R0 = R0;
316 cgrid.front().inks = cinks;
317 cgrid.front().total_ink = grand_total_ink;
318
319 if (Verbose > 1)
320 fprintf(stderr,
321 "level %d->%d, edges %d -> %d, ink %f->%f , gain = %f, or %f\n",
322 grid.front().level, cgrid.front().level, grid.front().n,
323 cgrid.front().n, grid.front().total_ink, grand_total_ink,
324 grid.front().total_ink - grand_total_ink, grand_total_gain);
325 assert(fabs(grid.front().total_ink - cgrid.front().total_ink -
326 grand_total_gain) <= 0.0001 * grid.front().total_ink);
327
328 Agglomerative_Ink_Bundling_establish(cgrid, pick, angle_param, angle);
329 grid.insert(grid.end(), cgrid.begin(), cgrid.end());
330
331 } else {
332 if (Verbose > 1)
333 fprintf(stderr,
334 "no more improvement, orig ink = %f, gain = %f, stop and final "
335 "bundling found\n",
336 grand_total_ink, grand_total_gain);
337 /* no more improvement, stop and final bundling found */
338 }
339}
340
342 const std::vector<pedge> &edges,
343 double angle_param, double angle) {
344 /* give a link of edges and their nearest neighbor graph, return a multilevel
345 * of edge bundling based on ink saving */
346 SparseMatrix A = A0;
347
348 if (!SparseMatrix_is_symmetric(A, false) || A->type != MATRIX_TYPE_REAL) {
350 }
352
353 std::vector<int> pick(A0->m);
354
355 Agglomerative_Ink_Bundling_establish(grid, pick.data(), angle_param, angle);
356
357 if (A != A0)
358 grid.front().delete_top_level_A = true; // be sure to clean up later
359
360 return grid;
361}
362
364 int dim, SparseMatrix A, std::vector<pedge> &edges, int nneighbors,
365 int *recurse_level, int MAX_RECURSE_LEVEL, double angle_param, double angle,
366 double *current_ink, double *ink00) {
367
368 int j, jj, k;
369 int *ia, *ja;
370 int *pick;
371 SparseMatrix R;
372 double ink0, ink1;
373 point_t meet1, meet2;
374 double wgt_all;
375 [[maybe_unused]] const double TOL = 0.0001;
376 clock_t start;
377
378 (*recurse_level)++;
379 if (Verbose > 1)
380 fprintf(stderr,
381 "agglomerative_ink_bundling_internal, recurse level ------- %d\n",
382 *recurse_level);
383
384 assert(A->m == (size_t)A->n);
385
386 start = clock();
387 aib_t grid = Agglomerative_Ink_Bundling_new(A, edges, angle_param, angle);
388 if (Verbose > 1)
389 fprintf(stderr, "CPU for agglomerative bundling %f\n",
390 (double)(clock() - start) / CLOCKS_PER_SEC);
391 ink0 = grid.front().total_ink;
392
393 /* find coarsest */
394 ink1 = grid.back().total_ink;
395
396 if (*current_ink < 0) {
397 *current_ink = *ink00 = ink0;
398 if (Verbose > 1)
399 fprintf(stderr, "initial total ink = %f\n", *current_ink);
400 }
401 if (ink1 < ink0) {
402 *current_ink -= ink0 - ink1;
403 }
404
405 if (Verbose > 1)
406 fprintf(stderr,
407 "ink: %f->%f, edges: %d->%d, current ink = %f, percentage gain "
408 "over original = %f\n",
409 ink0, ink1, grid.front().n, grid.back().n, *current_ink,
410 (ink0 - ink1) / *ink00);
411
412 /* if no meaningful improvement (0.0001%), out, else rebundle the middle
413 * section */
414 if ((ink0 - ink1) / *ink00 < 0.000001 || *recurse_level > MAX_RECURSE_LEVEL) {
415 /* project bundles up */
416 R = grid.back().R0;
417 if (R) {
418 ia = R->ia;
419 ja = R->ja;
420 for (size_t i = 0; i < R->m; i++) {
421 pick = &ja[ia[i]];
422
423 if (MINGLE_DEBUG)
424 if (Verbose)
425 fprintf(stderr, "calling ink2...\n");
426 ink1 = ink(edges, ia[i + 1] - ia[i], pick, &ink0, &meet1, &meet2,
427 angle_param, angle);
428 if (MINGLE_DEBUG)
429 if (Verbose)
430 fprintf(stderr, "finish calling ink2...\n");
431 assert(fabs(ink1 - grid.back().inks[i]) <= std::max(TOL, TOL * ink1) &&
432 ink1 - ink0 <= TOL);
433 assert(ink1 < 1000 * ink0); /* assert that points were found */
434 wgt_all = 0.;
435 if (ia[i + 1] - ia[i] > 1) {
436 for (j = ia[i]; j < ia[i + 1]; j++) {
437 /* make this edge 4 points, insert two meeting points at 1 and 2,
438 * make 3 the last point */
439 jj = ja[j];
440 pedge_double(edges[jj]); /* has to call pedge_double twice: from 2
441 points to 3 points to 5 points. The last
442 point not used, may be improved later */
443 pedge_double(edges[jj]);
444 pedge &e = edges[jj];
445
446 e.x[1 * dim] = meet1.x;
447 e.x[1 * dim + 1] = meet1.y;
448 e.x[2 * dim] = meet2.x;
449 e.x[2 * dim + 1] = meet2.y;
450 e.x[3 * dim] = e.x[4 * dim];
451 e.x[3 * dim + 1] = e.x[4 * dim + 1];
452 e.npoints = 4;
453 e.wgts = std::vector<double>(4, e.wgt);
454 wgt_all += e.wgt;
455 }
456 for (j = ia[i]; j < ia[i + 1]; j++) {
457 pedge &e = edges[ja[j]];
458 e.wgts[1] = wgt_all;
459 }
460 }
461 }
462 }
463 } else {
464 int ne, npp, l;
465 SparseMatrix A_mid;
466 double wgt;
467
468 /* make new edges using meet1 and meet2.
469
470 call Agglomerative_Ink_Bundling_new
471
472 inherit new edges to old edges
473 */
474
475 R = grid.back().R0;
476 assert(
477 R &&
478 grid.size() >
479 1); /* if ink improved, we should have gone at leat 1 level down! */
480 ia = R->ia;
481 ja = R->ja;
482 ne = R->m;
483 std::vector<pedge> mid_edges(ne);
484 std::vector<double> xx(4 * ne);
485 for (size_t i = 0; i < R->m; i++) {
486 pick = &ja[ia[i]];
487 wgt = 0.;
488 for (j = ia[i]; j < ia[i + 1]; j++)
489 wgt += edges[j].wgt;
490 if (MINGLE_DEBUG)
491 if (Verbose)
492 fprintf(stderr, "calling ink3...\n");
493 ink1 = ink(edges, ia[i + 1] - ia[i], pick, &ink0, &meet1, &meet2,
494 angle_param, angle);
495 if (MINGLE_DEBUG)
496 if (Verbose)
497 fprintf(stderr, "done calling ink3...\n");
498 assert(fabs(ink1 - grid.back().inks[i]) <= std::max(TOL, TOL * ink1) &&
499 ink1 - ink0 <= TOL);
500 assert(ink1 < 1000 * ink0); /* assert that points were found */
501 xx[i * 4 + 0] = meet1.x;
502 xx[i * 4 + 1] = meet1.y;
503 xx[i * 4 + 2] = meet2.x;
504 xx[i * 4 + 3] = meet2.y;
505 mid_edges[i] = pedge_wgt_new(2, dim, &xx.data()[i * 4], wgt);
506 }
507
508 A_mid = nearest_neighbor_graph(ne, std::min(nneighbors, ne), xx);
509
510 agglomerative_ink_bundling_internal(dim, A_mid, mid_edges, nneighbors,
511 recurse_level, MAX_RECURSE_LEVEL,
512 angle_param, angle, current_ink, ink00);
513 SparseMatrix_delete(A_mid);
514
515 /* patching edges with the new mid-section */
516 for (size_t i = 0; i < R->m; i++) {
517 // middle section of edges that will be bundled again
518 const pedge &midedge = mid_edges[i];
519 npp = midedge.npoints + 2;
520 for (j = ia[i]; j < ia[i + 1]; j++) {
521 jj = ja[j];
522 pedge_wgts_realloc(edges[jj], npp);
523 pedge &e = edges[jj];
524
525 assert(e.npoints == 2);
526 for (l = 0; l < dim; l++) { /* move the second point to the last */
527 e.x[(npp - 1) * dim + l] = e.x[1 * dim + l];
528 }
529
530 for (k = 0; k < midedge.npoints; k++) {
531 for (l = 0; l < dim; l++) {
532 e.x[(k + 1) * dim + l] = midedge.x[k * dim + l];
533 }
534 if (k < midedge.npoints - 1) {
535 if (!midedge.wgts.empty()) {
536 e.wgts[k + 1] = midedge.wgts[k];
537 } else {
538 e.wgts[k + 1] = midedge.wgt;
539 }
540 }
541 }
542 e.wgts[npp - 2] =
543 e.wgts[0]; // the last interval take from the 1st interval
544
545 e.npoints = npp;
546 }
547 }
548
549 for (int i = 0; i < ne; i++)
550 pedge_delete(mid_edges[i]);
551 }
552
554}
555
557 std::vector<pedge> &edges, int nneighbor,
558 int MAX_RECURSE_LEVEL, double angle_param,
559 double angle) {
560 int recurse_level = 0;
561 double current_ink = -1, ink0;
562
563 ink_count = 0;
564 agglomerative_ink_bundling_internal(dim, A, edges, nneighbor, &recurse_level,
565 MAX_RECURSE_LEVEL, angle_param, angle,
566 &current_ink, &ink0);
567
568 if (Verbose > 1)
569 fprintf(stderr,
570 "initial total ink = %f, final total ink = %f, inksaving = %f "
571 "percent, total ink_calc = %f, avg ink_calc per edge = %f\n",
572 ink0, current_ink, (ink0 - current_ink) / ink0, ink_count,
573 ink_count / (double)A->m);
574}
SparseMatrix SparseMatrix_new(size_t m, int n, size_t nz, int type, int format)
SparseMatrix SparseMatrix_from_coordinate_format(SparseMatrix A)
SparseMatrix SparseMatrix_transpose(SparseMatrix A)
bool SparseMatrix_is_symmetric(SparseMatrix A, bool test_pattern_symmetry_only)
SparseMatrix SparseMatrix_multiply(SparseMatrix A, SparseMatrix B)
void SparseMatrix_delete(SparseMatrix A)
SparseMatrix SparseMatrix_get_real_adjacency_matrix_symmetrized(SparseMatrix A)
@ MATRIX_TYPE_REAL
@ FORMAT_COORD
#define SparseMatrix_coordinate_form_add_entry(A, irn, jcn, val)
wrap SparseMatrix_coordinate_form_add_entry_ for type safety
static aib_t Agglomerative_Ink_Bundling_init(SparseMatrix A, const std::vector< pedge > &edges, int level)
void agglomerative_ink_bundling(int dim, SparseMatrix A, std::vector< pedge > &edges, int nneighbor, int MAX_RECURSE_LEVEL, double angle_param, double angle)
static aib_t Agglomerative_Ink_Bundling_new(SparseMatrix A0, const std::vector< pedge > &edges, double angle_param, double angle)
static void Agglomerative_Ink_Bundling_establish(aib_t &grid, int *pick, double angle_param, double angle)
static void agglomerative_ink_bundling_internal(int dim, SparseMatrix A, std::vector< pedge > &edges, int nneighbors, int *recurse_level, int MAX_RECURSE_LEVEL, double angle_param, double angle, double *current_ink, double *ink00)
static void Agglomerative_Ink_Bundling_delete(aib_t &grid)
std::vector< Agglomerative_Ink_Bundling > aib_t
pedge pedge_wgt_new(int np, int dim, double *x, double wgt)
void pedge_wgts_realloc(pedge &e, int n)
void pedge_double(pedge &e)
void pedge_delete(pedge &)
#define A(n, t)
Definition expr.h:76
#define A_
@ UNMATCHED
Definition general.h:75
static bool Verbose
Definition gml2gv.c:26
@ grid
Definition gvgen.c:34
#define B
Definition hierarchy.c:120
double ink(const std::vector< pedge > &edges, int numEdges, int *pick, double *ink0, point_t *meet1, point_t *meet2, double angle_param, double angle)
Definition ink.cpp:227
double ink_count
Definition ink.cpp:21
double ink1(const pedge &e)
Definition ink.cpp:308
SparseMatrix nearest_neighbor_graph(int nPts, int num_neighbors, const std::vector< double > &x)
static const int dim
size_t m
row dimension
std::vector< double > x
coordinates of the npoints poly points. Dimension dim*npoints
double wgt
int npoints
std::vector< double > wgts
Definition ink.h:16
double y
Definition ink.h:17
double x
Definition ink.h:17
graphs, nodes and edges info: Agraphinfo_t, Agnodeinfo_t and Agedgeinfo_t