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