Graphviz 14.1.6~dev.20260414.0102
Loading...
Searching...
No Matches
post_process.c
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 <assert.h>
14#include <common/globals.h>
15#include <common/types.h>
16#include <limits.h>
17#include <math.h>
18#include <stdbool.h>
19#include <stdlib.h>
20#include <string.h>
21#include <time.h>
22
23#include <neatogen/call_tri.h>
24#include <neatogen/overlap.h>
26#include <sfdpgen/sfdp.h>
29#include <util/alloc.h>
30#include <util/exit.h>
31#include <util/gv_math.h>
32#include <util/unused.h>
33
34#define node_degree(i) (ia[(i) + 1] - ia[(i)])
35
37 /* find the ideal distance between edges, either 1, or |N[i] \Union N[j]| -
38 * |N[i] \Intersection N[j]|
39 */
40 double sum;
41
42 assert(SparseMatrix_is_symmetric(A, false));
43
45 const int *const ia = D->ia;
46 const int *const ja = D->ja;
47 if (D->type != MATRIX_TYPE_REAL) {
48 free(D->a);
49 D->type = MATRIX_TYPE_REAL;
50 D->a = gv_calloc(D->nz, sizeof(double));
51 }
52 double *const d = D->a;
53
54 int *mask = gv_calloc(D->m, sizeof(int));
55 for (int i = 0; i < D->m; i++)
56 mask[i] = -1;
57
58 for (int i = 0; i < D->m; i++) {
59 const double di = node_degree(i);
60 mask[i] = i;
61 for (int j = ia[i]; j < ia[i + 1]; j++) {
62 if (i == ja[j])
63 continue;
64 mask[ja[j]] = i;
65 }
66 for (int j = ia[i]; j < ia[i + 1]; j++) {
67 const int k = ja[j];
68 if (i == k)
69 continue;
70 double len = di + node_degree(k);
71 for (int l = ia[k]; l < ia[k + 1]; l++) {
72 if (mask[ja[l]] == i)
73 len--;
74 }
75 d[j] = len;
76 assert(len > 0);
77 }
78 }
79
80 sum = 0;
81 double sumd = 0;
82 int nz = 0;
83 for (int i = 0; i < D->m; i++) {
84 for (int j = ia[i]; j < ia[i + 1]; j++) {
85 if (i == ja[j])
86 continue;
87 nz++;
88 sum += distance(x, dim, i, ja[j]);
89 sumd += d[j];
90 }
91 }
92 sum /= nz;
93 sumd /= nz;
94 sum = sum / sumd;
95
96 for (int i = 0; i < D->m; i++) {
97 for (int j = ia[i]; j < ia[i + 1]; j++) {
98 if (i == ja[j])
99 continue;
100 d[j] = sum * d[j];
101 }
102 }
103
104 free(mask);
105 return D;
106}
107
110 double *x, int ideal_dist_scheme) {
111 /* use up to dist 2 neighbor. This is used in overcoming pherical effect with
112 ideal distance of 2-neighbors equal graph distance etc.
113 */
114 int j, k, l, m = A->m, *ia = A->ia, *ja = A->ja, *iw, *jw, *id, *jd;
115 double *d, *w, *lambda;
116 double diag_d, diag_w, dist, s = 0, stop = 0, sbot = 0;
118
119 assert(SparseMatrix_is_symmetric(A, false));
120
122
125 sm->scaling = 1.;
126 sm->data = NULL;
128 sm->tol_cg = 0.01;
129 sm->maxit_cg = floor(sqrt(A->m));
130
131 lambda = sm->lambda = gv_calloc(m, sizeof(double));
132 for (int i = 0; i < m; i++)
133 sm->lambda[i] = lambda0;
134 int *mask = gv_calloc(m, sizeof(int));
135
136 double *avg_dist = gv_calloc(m, sizeof(double));
137
138 for (int i = 0; i < m; i++) {
139 avg_dist[i] = 0;
140 int nz = 0;
141 for (j = ia[i]; j < ia[i + 1]; j++) {
142 if (i == ja[j])
143 continue;
144 avg_dist[i] += distance(x, dim, i, ja[j]);
145 nz++;
146 }
147 assert(nz > 0);
148 avg_dist[i] /= nz;
149 }
150
151 for (int i = 0; i < m; i++)
152 mask[i] = -1;
153
154 size_t nz = 0;
155 for (int i = 0; i < m; i++) {
156 mask[i] = i;
157 for (j = ia[i]; j < ia[i + 1]; j++) {
158 k = ja[j];
159 if (mask[k] != i) {
160 mask[k] = i;
161 nz++;
162 }
163 }
164 for (j = ia[i]; j < ia[i + 1]; j++) {
165 k = ja[j];
166 for (l = ia[k]; l < ia[k + 1]; l++) {
167 if (mask[ja[l]] != i) {
168 mask[ja[l]] = i;
169 nz++;
170 }
171 }
172 }
173 }
174
175 sm->Lw = SparseMatrix_new(m, m, nz + (size_t)m, MATRIX_TYPE_REAL, FORMAT_CSR);
176 assert(sm->Lw != NULL);
177 sm->Lwd =
178 SparseMatrix_new(m, m, nz + (size_t)m, MATRIX_TYPE_REAL, FORMAT_CSR);
179 assert(sm->Lwd != NULL);
180
181 iw = sm->Lw->ia;
182 jw = sm->Lw->ja;
183
184 w = sm->Lw->a;
185 d = sm->Lwd->a;
186
187 id = sm->Lwd->ia;
188 jd = sm->Lwd->ja;
189 iw[0] = id[0] = 0;
190
191 nz = 0;
192 for (int i = 0; i < m; i++) {
193 mask[i] = i + m;
194 diag_d = diag_w = 0;
195 for (j = ia[i]; j < ia[i + 1]; j++) {
196 k = ja[j];
197 if (mask[k] != i + m) {
198 mask[k] = i + m;
199
200 jw[nz] = k;
201 if (ideal_dist_scheme == IDEAL_GRAPH_DIST) {
202 dist = 1;
203 } else if (ideal_dist_scheme == IDEAL_AVG_DIST) {
204 dist = (avg_dist[i] + avg_dist[k]) * 0.5;
205 } else if (ideal_dist_scheme == IDEAL_POWER_DIST) {
206 dist = pow(distance_cropped(x, dim, i, k), .4);
207 } else {
208 fprintf(stderr, "ideal_dist_scheme value wrong");
209 assert(0);
210 graphviz_exit(1);
211 }
212
213 /*
214 w[nz] = -1./(ia[i+1]-ia[i]+ia[ja[j]+1]-ia[ja[j]]);
215 w[nz] = -2./(avg_dist[i]+avg_dist[k]);*/
216 /* w[nz] = -1.;*/ /* use unit weight for now, later can try
217 1/(deg(i)+deg(k)) */
218 w[nz] = -1 / (dist * dist);
219
220 diag_w += w[nz];
221
222 jd[nz] = k;
223 /*
224 d[nz] = w[nz]*distance(x,dim,i,k);
225 d[nz] = w[nz]*dd[j];
226 d[nz] = w[nz]*(avg_dist[i] + avg_dist[k])*0.5;
227 */
228 d[nz] = w[nz] * dist;
229 stop += d[nz] * distance(x, dim, i, k);
230 sbot += d[nz] * dist;
231 diag_d += d[nz];
232
233 nz++;
234 }
235 }
236
237 /* distance 2 neighbors */
238 for (j = ia[i]; j < ia[i + 1]; j++) {
239 k = ja[j];
240 for (l = ia[k]; l < ia[k + 1]; l++) {
241 if (mask[ja[l]] != i + m) {
242 mask[ja[l]] = i + m;
243
244 if (ideal_dist_scheme == IDEAL_GRAPH_DIST) {
245 dist = 2;
246 } else if (ideal_dist_scheme == IDEAL_AVG_DIST) {
247 dist = (avg_dist[i] + 2 * avg_dist[k] + avg_dist[ja[l]]) * 0.5;
248 } else if (ideal_dist_scheme == IDEAL_POWER_DIST) {
249 dist = pow(distance_cropped(x, dim, i, ja[l]), .4);
250 } else {
251 fprintf(stderr, "ideal_dist_scheme value wrong");
252 assert(0);
253 graphviz_exit(1);
254 }
255
256 jw[nz] = ja[l];
257 /*
258 w[nz] = -1/(ia[i+1]-ia[i]+ia[ja[l]+1]-ia[ja[l]]);
259 w[nz] = -2/(avg_dist[i] + 2*avg_dist[k] + avg_dist[ja[l]]);*/
260 /* w[nz] = -1.;*/ /* use unit weight for now, later can try
261 1/(deg(i)+deg(k)) */
262
263 w[nz] = -1 / (dist * dist);
264
265 diag_w += w[nz];
266
267 jd[nz] = ja[l];
268 /*
269 d[nz] = w[nz]*(distance(x,dim,i,k)+distance(x,dim,k,ja[l]));
270 d[nz] = w[nz]*(dd[j]+dd[l]);
271 d[nz] = w[nz]*(avg_dist[i] + 2*avg_dist[k] + avg_dist[ja[l]])*0.5;
272 */
273 d[nz] = w[nz] * dist;
274 stop += d[nz] * distance(x, dim, ja[l], k);
275 sbot += d[nz] * dist;
276 diag_d += d[nz];
277
278 nz++;
279 }
280 }
281 }
282 jw[nz] = i;
283 lambda[i] *= (-diag_w); /* alternatively don't do that then we have a
284 constant penalty term scaled by lambda0 */
285
286 w[nz] = -diag_w + lambda[i];
287 jd[nz] = i;
288 d[nz] = -diag_d;
289 nz++;
290
291 assert(nz <= INT_MAX);
292 iw[i + 1] = (int)nz;
293 id[i + 1] = (int)nz;
294 }
295 s = stop / sbot;
296 for (size_t i = 0; i < nz; i++)
297 d[i] *= s;
298
299 sm->scaling = s;
300 sm->Lw->nz = nz;
301 sm->Lwd->nz = nz;
302
303 free(mask);
304 free(avg_dist);
306 return sm;
307}
308
311 /* solve a stress model to achieve the ideal distance among a sparse set of
312 edges recorded in A. A must be a real matrix.
313 */
314 int m = A->m;
315 double stop = 0, sbot = 0;
316
317 assert(SparseMatrix_is_symmetric(A, false) && A->type == MATRIX_TYPE_REAL);
318
319 /* if x is all zero, make it random */
320 bool has_nonzero = false;
321 for (int i = 0; i < m * dim; i++) {
322 if (!is_exactly_equal(x[i], 0)) {
323 has_nonzero = true;
324 break;
325 }
326 }
327 if (!has_nonzero) {
328 for (int i = 0; i < m * dim; i++)
329 x[i] = 72 * drand();
330 }
331
332 const int *const ia = A->ia;
333 const int *const ja = A->ja;
334 const double *const a = A->a;
335
338 sm->scaling = 1.;
339 sm->data = NULL;
341 sm->D = A;
342 sm->tol_cg = 0.01;
343 sm->maxit_cg = floor(sqrt(A->m));
344
345 double *const lambda = sm->lambda = gv_calloc(m, sizeof(double));
346
347 size_t nz = A->nz;
348
349 sm->Lw = SparseMatrix_new(m, m, nz + (size_t)m, MATRIX_TYPE_REAL, FORMAT_CSR);
350 assert(sm->Lw != NULL);
351 sm->Lwd =
352 SparseMatrix_new(m, m, nz + (size_t)m, MATRIX_TYPE_REAL, FORMAT_CSR);
353 assert(sm->Lwd != NULL);
354
355 int *const iw = sm->Lw->ia;
356 int *const jw = sm->Lw->ja;
357 int *const id = sm->Lwd->ia;
358 int *const jd = sm->Lwd->ja;
359 double *const w = sm->Lw->a;
360 double *const d = sm->Lwd->a;
361 iw[0] = id[0] = 0;
362
363 nz = 0;
364 for (int i = 0; i < m; i++) {
365 double diag_d = 0;
366 double diag_w = 0;
367 for (int j = ia[i]; j < ia[i + 1]; j++) {
368 const int k = ja[j];
369 if (k != i) {
370
371 jw[nz] = k;
372 const double dist = a[j];
373 w[nz] = -1;
374 diag_w += w[nz];
375 jd[nz] = k;
376 d[nz] = w[nz] * dist;
377
378 stop += d[nz] * distance(x, dim, i, k);
379 sbot += d[nz] * dist;
380 diag_d += d[nz];
381
382 nz++;
383 }
384 }
385
386 jw[nz] = i;
387 lambda[i] *= (-diag_w); /* alternatively don't do that then we have a
388 constant penalty term scaled by lambda0 */
389 w[nz] = -diag_w + lambda[i];
390
391 jd[nz] = i;
392 d[nz] = -diag_d;
393 nz++;
394
395 assert(nz <= INT_MAX);
396 iw[i + 1] = (int)nz;
397 id[i + 1] = (int)nz;
398 }
399 const double s = stop / sbot;
400 if (s == 0) {
402 return NULL;
403 }
404 for (size_t i = 0; i < nz; i++)
405 d[i] *= s;
406
407 sm->scaling = s;
408 sm->Lw->nz = nz;
409 sm->Lwd->nz = nz;
410
411 return sm;
412}
413
414static double total_distance(int m, int dim, double *x, double *y) {
415 double total = 0, dist = 0;
416 int i, j;
417
418 for (i = 0; i < m; i++) {
419 dist = 0.;
420 for (j = 0; j < dim; j++) {
421 dist +=
422 (y[i * dim + j] - x[i * dim + j]) * (y[i * dim + j] - x[i * dim + j]);
423 }
424 total += sqrt(dist);
425 }
426 return total;
427}
428
433
434double
436 int dim, double *x, int maxit_sm) {
437 return StressMajorizationSmoother_smooth(sm, dim, x, maxit_sm);
438}
439
441 int dim, double *x, SparseMatrix *LL,
442 double **rhs) {
443 int edge_labeling_scheme = data->edge_labeling_scheme;
444 int n_constr_nodes = data->n_constr_nodes;
445 int *constr_nodes = data->constr_nodes;
446 SparseMatrix A_constr = data->A_constr;
447 int *ia = A_constr->ia, *ja = A_constr->ja, ii, jj, l, ll, i, j;
448 int *irn = data->irn, *jcn = data->jcn;
449 double *val = data->val, dist, kk, k;
450 double *x00 = NULL;
451 SparseMatrix Lc = NULL;
452 double constr_penalty = data->constr_penalty;
453
454 if (edge_labeling_scheme == ELSCHEME_PENALTY ||
455 edge_labeling_scheme == ELSCHEME_STRAIGHTLINE_PENALTY) {
456 /* for an node with two neighbors j--i--k, and assume i needs to be between
457 j and k, then the contribution to P is . i j k i 1 -0.5 -0.5
458 j -0.5 0.25 0.25
459 k -0.5 0.25 0.25
460 in general, if i has m neighbors j, k, ..., then
461 pᵢᵢ = 1
462 pⱼⱼ = 1 ÷ m²
463 pᵢⱼ = -1 ÷ m
464 pⱼₖ = 1 ÷ m²
465 . i j k
466 i 1 -1 ÷ m -1 ÷ m ...
467 j -1 ÷ m 1 ÷ m² 1 ÷ m² ...
468 k -1 ÷ m 1 ÷ m² 1 ÷ m² ...
469 */
470 if (!irn) {
471 assert((!jcn) && (!val));
472 size_t nz = 0;
473 for (i = 0; i < n_constr_nodes; i++) {
474 ii = constr_nodes[i];
475 k = ia[ii + 1] - ia[ii]; /*usually k = 2 */
476 nz += (size_t)((k + 1) * (k + 1));
477 }
478 irn = data->irn = gv_calloc(nz, sizeof(int));
479 jcn = data->jcn = gv_calloc(nz, sizeof(int));
480 val = data->val = gv_calloc(nz, sizeof(double));
481 }
482 size_t nz = 0;
483 for (i = 0; i < n_constr_nodes; i++) {
484 ii = constr_nodes[i];
485 jj = ja[ia[ii]];
486 ll = ja[ia[ii] + 1];
487 if (jj == ll)
488 continue; /* do not do loops */
489 dist = distance_cropped(x, dim, jj, ll);
490 dist *= dist;
491
492 k = ia[ii + 1] - ia[ii]; /* usually k = 2 */
493 kk = k * k;
494 irn[nz] = ii;
495 jcn[nz] = ii;
496 val[nz++] = constr_penalty / (dist);
497 k = constr_penalty / (k * dist);
498 kk = constr_penalty / (kk * dist);
499 for (j = ia[ii]; j < ia[ii + 1]; j++) {
500 irn[nz] = ii;
501 jcn[nz] = ja[j];
502 val[nz++] = -k;
503 }
504 for (j = ia[ii]; j < ia[ii + 1]; j++) {
505 jj = ja[j];
506 irn[nz] = jj;
507 jcn[nz] = ii;
508 val[nz++] = -k;
509 for (l = ia[ii]; l < ia[ii + 1]; l++) {
510 ll = ja[l];
511 irn[nz] = jj;
512 jcn[nz] = ll;
513 val[nz++] = kk;
514 }
515 }
516 }
517 Lc = SparseMatrix_from_coordinate_arrays(nz, m, m, irn, jcn, val,
518 MATRIX_TYPE_REAL, sizeof(double));
519 } else if (edge_labeling_scheme == ELSCHEME_PENALTY2 ||
520 edge_labeling_scheme == ELSCHEME_STRAIGHTLINE_PENALTY2) {
521 /* for an node with two neighbors j--i--k, and assume i needs to be between
522 the old position of j and k, then the contribution to P is 1/d_jk, and to
523 the right hand side: {0,...,average_position_of_i's neighbor if i is an
524 edge node,...}
525 */
526 if (!irn) {
527 assert((!jcn) && (!val));
528 int nz = n_constr_nodes;
529 irn = data->irn = gv_calloc(nz, sizeof(int));
530 jcn = data->jcn = gv_calloc(nz, sizeof(int));
531 val = data->val = gv_calloc(nz, sizeof(double));
532 }
533 x00 = gv_calloc(m * dim, sizeof(double));
534 size_t nz = 0;
535 for (i = 0; i < n_constr_nodes; i++) {
536 ii = constr_nodes[i];
537 jj = ja[ia[ii]];
538 ll = ja[ia[ii] + 1];
539 dist = distance_cropped(x, dim, jj, ll);
540 irn[nz] = ii;
541 jcn[nz] = ii;
542 val[nz++] = constr_penalty / (dist);
543 for (j = ia[ii]; j < ia[ii + 1]; j++) {
544 jj = ja[j];
545 for (l = 0; l < dim; l++) {
546 x00[ii * dim + l] += x[jj * dim + l];
547 }
548 }
549 for (l = 0; l < dim; l++) {
550 x00[ii * dim + l] *= constr_penalty / (dist) / (ia[ii + 1] - ia[ii]);
551 }
552 }
553 Lc = SparseMatrix_from_coordinate_arrays(nz, m, m, irn, jcn, val,
554 MATRIX_TYPE_REAL, sizeof(double));
555 }
556 *LL = Lc;
557 *rhs = x00;
558}
559
560static UNUSED double get_stress(int m, int dim, int *iw, int *jw, double *w,
561 double *d, double *x, double scaling) {
562 int i, j;
563 double res = 0., dist;
564 // We use the fact that dᵢⱼ = wᵢⱼ × graph_dist(i, j). Also, dᵢⱼ and x are
565 // scaled by ×scaling, so divide by it to get actual unscaled stress.
566 for (i = 0; i < m; i++) {
567 for (j = iw[i]; j < iw[i + 1]; j++) {
568 if (i == jw[j]) {
569 continue;
570 }
571 dist = d[j] / w[j]; /* both negative*/
572 res += -w[j] * (dist - distance(x, dim, i, jw[j])) *
573 (dist - distance(x, dim, i, jw[j]));
574 }
575 }
576 return 0.5 * res / scaling / scaling;
577}
578
580 double *x, int maxit_sm) {
581 SparseMatrix Lw = sm->Lw, Lwd = sm->Lwd, Lwdd = NULL;
582 int i, j, k, m, *id, *jd, *iw, *jw, idiag, iter = 0;
583 double *w, *dd, *d, *y = NULL, *x0 = NULL, *x00 = NULL, diag, diff = 1,
584 *lambda = sm->lambda;
585 SparseMatrix Lc = NULL;
586 double dij, dist;
587
588 const double tol = 0.001;
589
590 Lwdd = SparseMatrix_copy(Lwd);
591 m = Lw->m;
592 x0 = calloc(dim * m, sizeof(double));
593 if (!x0)
594 goto RETURN;
595
596 memcpy(x0, x, sizeof(double) * dim * m);
597 y = calloc(dim * m, sizeof(double));
598 if (!y)
599 goto RETURN;
600
601 id = Lwd->ia;
602 jd = Lwd->ja;
603 d = Lwd->a;
604 dd = Lwdd->a;
605 w = Lw->a;
606 iw = Lw->ia;
607 jw = Lw->ja;
608
609#ifdef DEBUG_PRINT
610 if (Verbose)
611 fprintf(stderr, "initial stress = %f\n",
612 get_stress(m, dim, iw, jw, w, d, x, sm->scaling));
613#else
614 (void)iw;
615 (void)jw;
616#endif
617 /* for the additional matrix L due to the position constraints */
618 if (sm->scheme == SM_SCHEME_NORMAL_ELABEL) {
619 get_edge_label_matrix(sm->data, m, dim, x, &Lc, &x00);
620 if (Lc)
621 Lw = SparseMatrix_add(Lw, Lc);
622 }
623
624 while (iter++ < maxit_sm && diff > tol) {
625
626 for (i = 0; i < m; i++) {
627 idiag = -1;
628 diag = 0.;
629 for (j = id[i]; j < id[i + 1]; j++) {
630 if (i == jd[j]) {
631 idiag = j;
632 continue;
633 }
634
635 dist = distance(x, dim, i, jd[j]);
636 if (d[j] == 0) {
637 dd[j] = 0;
638 } else {
639 if (dist == 0) {
640 dij = d[j] / w[j]; /* the ideal distance */
641 /* perturb so points do not sit at the same place */
642 for (k = 0; k < dim; k++)
643 x[jd[j] * dim + k] += 0.0001 * (drand() + .0001) * dij;
644 dist = distance(x, dim, i, jd[j]);
645 }
646 dd[j] = d[j] / dist;
647 }
648 diag += dd[j];
649 }
650 assert(idiag >= 0);
651 dd[idiag] = -diag;
652 }
653 /* solve (Lw+lambda*I) x = Lwdd y + lambda x0 */
654
655 SparseMatrix_multiply_dense(Lwdd, x, y, dim);
656
657 if (lambda) { /* is there a penalty term? */
658 for (i = 0; i < m; i++) {
659 for (j = 0; j < dim; j++) {
660 y[i * dim + j] += lambda[i] * x0[i * dim + j];
661 }
662 }
663 }
664
665 /* additional term added to the rhs */
666 switch (sm->scheme) {
668 for (i = 0; i < m; i++) {
669 for (j = 0; j < dim; j++) {
670 y[i * dim + j] += x00[i * dim + j];
671 }
672 }
673 break;
674 }
675 default:
676 break;
677 }
678
679#ifdef DEBUG_PRINT
680 if (Verbose) {
681 fprintf(stderr, "stress1 = %g\n",
682 get_stress(m, dim, iw, jw, w, d, x, sm->scaling));
683 }
684#endif
685
686 SparseMatrix_solve(Lw, dim, x, y, sm->tol_cg, sm->maxit_cg);
687
688#ifdef DEBUG_PRINT
689 if (Verbose)
690 fprintf(stderr, "stress2 = %g\n",
691 get_stress(m, dim, iw, jw, w, d, y, sm->scaling));
692#endif
693 diff = total_distance(m, dim, x, y) / sqrt(vector_product(m * dim, x, x));
694#ifdef DEBUG_PRINT
695 if (Verbose) {
696 fprintf(stderr,
697 "Outer iter = %d, cg res = %g, ||x_{k+1}-x_k||/||x_k|| = %g\n",
698 iter, res, diff);
699 }
700#endif
701
702 memcpy(x, y, sizeof(double) * m * dim);
703 }
704
705#ifdef DEBUG
706 _statistics[1] += iter - 1;
707#endif
708
709#ifdef DEBUG_PRINT
710 if (Verbose)
711 fprintf(stderr, "iter = %d, final stress = %f\n", iter,
712 get_stress(m, dim, iw, jw, w, d, x, sm->scaling));
713#endif
714
715RETURN:
717 if (Lc) {
720 }
721
722 free(x0);
723 free(y);
724 free(x00);
725 return diff;
726}
727
729 if (!sm)
730 return;
731 if (sm->Lw)
733 if (sm->Lwd)
735 free(sm->lambda);
736 if (sm->data)
737 sm->data_deallocator(sm->data);
738 free(sm);
739}
740
742 bool use_triangularization) {
743 int i, j, k, m = A->m, *ia = A->ia, *ja = A->ja, *iw, *jw, jdiag, nz;
745 double *d, *w, diag_d, diag_w, dist;
746 double s = 0, stop = 0, sbot = 0;
747
748 assert(SparseMatrix_is_symmetric(A, false));
749
750 double *avg_dist = gv_calloc(m, sizeof(double));
751
752 for (i = 0; i < m; i++) {
753 avg_dist[i] = 0;
754 nz = 0;
755 for (j = ia[i]; j < ia[i + 1]; j++) {
756 if (i == ja[j])
757 continue;
758 avg_dist[i] += distance(x, dim, i, ja[j]);
759 nz++;
760 }
761 assert(nz > 0);
762 avg_dist[i] /= nz;
763 }
764
766 sm->scaling = 1;
767 sm->data = NULL;
769 sm->tol_cg = 0.01;
770 sm->maxit_cg = floor(sqrt(A->m));
771
772 double *lambda = sm->lambda = gv_calloc(m, sizeof(double));
773
774 if (m > 2) {
775 if (use_triangularization) {
776 B = call_tri(m, x);
777 } else {
778 B = call_tri2(m, dim, x);
779 }
780 } else {
782 }
783
784 sm->Lw = SparseMatrix_add(A, B);
785
787 sm->Lwd = SparseMatrix_copy(sm->Lw);
788 if (!sm->Lw || !sm->Lwd) {
790 return NULL;
791 }
792
793 iw = sm->Lw->ia;
794 jw = sm->Lw->ja;
795
796 w = sm->Lw->a;
797 d = sm->Lwd->a;
798
799 for (i = 0; i < m; i++) {
800 diag_d = diag_w = 0;
801 jdiag = -1;
802 for (j = iw[i]; j < iw[i + 1]; j++) {
803 k = jw[j];
804 if (k == i) {
805 jdiag = j;
806 continue;
807 }
808 /* w[j] = -1./(ia[i+1]-ia[i]+ia[ja[j]+1]-ia[ja[j]]);
809 w[j] = -2./(avg_dist[i]+avg_dist[k]);
810 w[j] = -1.*/
811 ; /* use unit weight for now, later can try 1/(deg(i)+deg(k)) */
812 dist = pow(distance_cropped(x, dim, i, k), 0.6);
813 w[j] = 1 / (dist * dist);
814 diag_w += w[j];
815
816 /* d[j] = w[j]*distance(x,dim,i,k);
817 d[j] = w[j]*(avg_dist[i] + avg_dist[k])*0.5;*/
818 d[j] = w[j] * dist;
819 stop += d[j] * distance(x, dim, i, k);
820 sbot += d[j] * dist;
821 diag_d += d[j];
822 }
823
824 lambda[i] *= (-diag_w);
825
826 assert(jdiag >= 0);
827 w[jdiag] = -diag_w + lambda[i];
828 d[jdiag] = -diag_d;
829 }
830
831 s = stop / sbot;
832 for (i = 0; i < iw[m]; i++)
833 d[i] *= s;
834 sm->scaling = s;
835
836 free(avg_dist);
837
838 return sm;
839}
840
845
849
850/* ================================ spring and spring-electrical based smoother
851 * ================ */
853 spring_electrical_control ctrl, double *x) {
854 int j, k, l, m = A->m, *ia = A->ia, *ja = A->ja, *id, *jd;
855 double *d, *dd;
857
858 assert(SparseMatrix_is_symmetric(A, false));
859
861 dd = ID->a;
862
863 SpringSmoother sm = gv_alloc(sizeof(struct SpringSmoother_struct));
864 int *mask = gv_calloc(m, sizeof(int));
865
866 double *avg_dist = gv_calloc(m, sizeof(double));
867
868 for (int i = 0; i < m; i++) {
869 avg_dist[i] = 0;
870 int nz = 0;
871 for (j = ia[i]; j < ia[i + 1]; j++) {
872 if (i == ja[j])
873 continue;
874 avg_dist[i] += distance(x, dim, i, ja[j]);
875 nz++;
876 }
877 assert(nz > 0);
878 avg_dist[i] /= nz;
879 }
880
881 for (int i = 0; i < m; i++)
882 mask[i] = -1;
883
884 size_t nz = 0;
885 for (int i = 0; i < m; i++) {
886 mask[i] = i;
887 for (j = ia[i]; j < ia[i + 1]; j++) {
888 k = ja[j];
889 if (mask[k] != i) {
890 mask[k] = i;
891 nz++;
892 }
893 }
894 for (j = ia[i]; j < ia[i + 1]; j++) {
895 k = ja[j];
896 for (l = ia[k]; l < ia[k + 1]; l++) {
897 if (mask[ja[l]] != i) {
898 mask[ja[l]] = i;
899 nz++;
900 }
901 }
902 }
903 }
904
906 assert(sm->D != NULL);
907
908 id = sm->D->ia;
909 jd = sm->D->ja;
910 d = sm->D->a;
911 id[0] = 0;
912
913 nz = 0;
914 for (int i = 0; i < m; i++) {
915 mask[i] = i + m;
916 for (j = ia[i]; j < ia[i + 1]; j++) {
917 k = ja[j];
918 if (mask[k] != i + m) {
919 mask[k] = i + m;
920 jd[nz] = k;
921 d[nz] = (avg_dist[i] + avg_dist[k]) * 0.5;
922 d[nz] = dd[j];
923 nz++;
924 }
925 }
926
927 for (j = ia[i]; j < ia[i + 1]; j++) {
928 k = ja[j];
929 for (l = ia[k]; l < ia[k + 1]; l++) {
930 if (mask[ja[l]] != i + m) {
931 mask[ja[l]] = i + m;
932 jd[nz] = ja[l];
933 d[nz] = (avg_dist[i] + 2 * avg_dist[k] + avg_dist[ja[l]]) * 0.5;
934 d[nz] = dd[j] + dd[l];
935 nz++;
936 }
937 }
938 }
939 assert(nz <= INT_MAX);
940 id[i + 1] = (int)nz;
941 }
942 sm->D->nz = nz;
943 sm->ctrl = ctrl;
944 sm->ctrl.random_start = false;
945 sm->ctrl.multilevels = 1;
946 sm->ctrl.step /= 2;
947 sm->ctrl.maxiter = 20;
948
949 free(mask);
950 free(avg_dist);
952
953 return sm;
954}
955
957 if (!sm)
958 return;
959 if (sm->D)
961}
962
964 double *x) {
965 int flag = 0;
966
967 spring_electrical_spring_embedding(dim, A, sm->D, &sm->ctrl, x, &flag);
968 assert(!flag);
969}
970
971/*=============================== end of spring and spring-electrical based
972 * smoother =========== */
973
975 spring_electrical_control ctrl, double *x) {
976#ifdef TIME
977 clock_t cpu;
978#endif
979
980#ifdef TIME
981 cpu = clock();
982#endif
983
984 switch (ctrl.smoothing) {
985 case SMOOTHING_RNG:
986 case SMOOTHING_TRIANGLE: {
988
989 if (A->m > 2) { /* triangles need at least 3 nodes */
990 if (ctrl.smoothing == SMOOTHING_RNG) {
991 sm = TriangleSmoother_new(A, dim, x, false);
992 } else {
993 sm = TriangleSmoother_new(A, dim, x, true);
994 }
997 }
998 break;
999 }
1004 int dist_scheme = IDEAL_AVG_DIST;
1005
1007 dist_scheme = IDEAL_GRAPH_DIST;
1009 dist_scheme = IDEAL_AVG_DIST;
1011 dist_scheme = IDEAL_POWER_DIST;
1012 }
1013
1014 sm = StressMajorizationSmoother2_new(A, dim, 0.05, x, dist_scheme);
1017 break;
1018 }
1019 case SMOOTHING_SPRING: {
1020 SpringSmoother sm = SpringSmoother_new(A, dim, ctrl, x);
1021 SpringSmoother_smooth(sm, A, dim, x);
1023
1024 break;
1025 }
1026
1027 } /* end switch between smoothing methods */
1028
1029#ifdef TIME
1030 if (Verbose)
1031 fprintf(stderr, "post processing %f\n",
1032 ((double)(clock() - cpu)) / CLOCKS_PER_SEC);
1033#endif
1034}
void SparseMatrix_multiply_dense(SparseMatrix A, const double *v, double *res, int dim)
bool SparseMatrix_is_symmetric(SparseMatrix A, bool test_pattern_symmetry_only)
SparseMatrix SparseMatrix_from_coordinate_arrays(size_t nz, int m, int n, int *irn, int *jcn, const void *val, int type, size_t sz)
void SparseMatrix_delete(SparseMatrix A)
SparseMatrix SparseMatrix_copy(SparseMatrix A)
SparseMatrix SparseMatrix_add(SparseMatrix A, SparseMatrix B)
SparseMatrix SparseMatrix_new(int m, int n, size_t nz, int type, int format)
@ FORMAT_CSR
@ MATRIX_TYPE_REAL
Memory allocation wrappers that exit on failure.
static void * gv_calloc(size_t nmemb, size_t size)
Definition alloc.h:26
static void * gv_alloc(size_t size)
Definition alloc.h:47
SparseMatrix call_tri2(int n, int dim, double *xx)
Definition call_tri.c:64
SparseMatrix call_tri(int n, double *x)
Definition call_tri.c:19
static NORETURN void graphviz_exit(int status)
Definition exit.h:23
#define A(n, t)
Definition expr.h:76
static double dist(int dim, double *x, double *y)
double drand(void)
Definition general.c:24
double distance(double *x, int dim, int i, int j)
Definition general.c:123
double vector_product(int n, double *x, double *y)
Definition general.c:34
double distance_cropped(double *x, int dim, int i, int j)
Definition general.c:118
static double len(glCompPoint p)
Definition glutils.c:138
static bool Verbose
Definition gml2gv.c:26
void free(void *)
#define ID
Definition gmlparse.h:134
node NULL
Definition grammar.y:181
static uint64_t id
Definition gv2gml.c:42
Arithmetic helper functions.
static bool is_exactly_equal(double a, double b)
are two values precisely the same?
Definition gv_math.h:48
#define B
Definition hierarchy.c:120
#define D
Definition hierarchy.c:122
static const int dim
@ ELSCHEME_PENALTY2
Definition overlap.h:17
@ ELSCHEME_STRAIGHTLINE_PENALTY2
Definition overlap.h:17
@ ELSCHEME_STRAIGHTLINE_PENALTY
Definition overlap.h:17
@ ELSCHEME_PENALTY
Definition overlap.h:17
static void get_edge_label_matrix(relative_position_constraints data, int m, int dim, double *x, SparseMatrix *LL, double **rhs)
StressMajorizationSmoother SparseStressMajorizationSmoother_new(SparseMatrix A, int dim, double *x)
static double total_distance(int m, int dim, double *x, double *y)
static SparseMatrix ideal_distance_matrix(SparseMatrix A, int dim, double *x)
void SparseStressMajorizationSmoother_delete(SparseStressMajorizationSmoother sm)
TriangleSmoother TriangleSmoother_new(SparseMatrix A, int dim, double *x, bool use_triangularization)
double StressMajorizationSmoother_smooth(StressMajorizationSmoother sm, int dim, double *x, int maxit_sm)
void SpringSmoother_delete(SpringSmoother sm)
double SparseStressMajorizationSmoother_smooth(SparseStressMajorizationSmoother sm, int dim, double *x, int maxit_sm)
void post_process_smoothing(int dim, SparseMatrix A, spring_electrical_control ctrl, double *x)
void TriangleSmoother_smooth(TriangleSmoother sm, int dim, double *x)
void StressMajorizationSmoother_delete(StressMajorizationSmoother sm)
SpringSmoother SpringSmoother_new(SparseMatrix A, int dim, spring_electrical_control ctrl, double *x)
StressMajorizationSmoother StressMajorizationSmoother2_new(SparseMatrix A, int dim, double lambda0, double *x, int ideal_dist_scheme)
static UNUSED double get_stress(int m, int dim, int *iw, int *jw, double *w, double *d, double *x, double scaling)
void TriangleSmoother_delete(TriangleSmoother sm)
void SpringSmoother_smooth(SpringSmoother sm, SparseMatrix A, int dim, double *x)
#define node_degree(i)
#define TriangleSmoother_struct
@ SM_SCHEME_NORMAL_ELABEL
@ SM_SCHEME_NORMAL
@ IDEAL_GRAPH_DIST
@ IDEAL_AVG_DIST
@ IDEAL_POWER_DIST
double SparseMatrix_solve(SparseMatrix A, int dim, double *x0, double *rhs, double tol, double maxit)
static const double tol
void spring_electrical_spring_embedding(int dim, SparseMatrix A0, SparseMatrix D, spring_electrical_control *ctrl, double *x, int *flag)
@ SMOOTHING_SPRING
@ SMOOTHING_RNG
@ SMOOTHING_STRESS_MAJORIZATION_GRAPH_DIST
@ SMOOTHING_STRESS_MAJORIZATION_POWER_DIST
@ SMOOTHING_TRIANGLE
@ SMOOTHING_STRESS_MAJORIZATION_AVG_DIST
#define RETURN(v)
Definition strmatch.c:146
size_t nz
the actual length used is nz, for CSR/CSC matrix this is the same as ia[n]
spring_electrical_control ctrl
SparseMatrix Lwd
the laplacian like matrix with offdiag = -scaling × dᵢⱼ ÷ wᵢⱼ. RHS in stress majorization = Lwd....
SparseMatrix Lw
the weighted laplacian. with offdiag = -1 ÷ wᵢⱼ
bool random_start
whether to apply SE from a random layout, or from existing layout
graphs, nodes and edges info: Agraphinfo_t, Agnodeinfo_t and Agedgeinfo_t
Definition grammar.c:90
abstraction for squashing compiler warnings for unused symbols
#define UNUSED
Definition unused.h:25