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