Graphviz 15.1.1~dev.20260630.1303
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 <stdint.h>
20#include <stdlib.h>
21#include <string.h>
22#include <time.h>
23
24#include <neatogen/call_tri.h>
25#include <neatogen/overlap.h>
27#include <sfdpgen/sfdp.h>
30#include <util/alloc.h>
31#include <util/exit.h>
32#include <util/gv_math.h>
33#include <util/unused.h>
34
35#define node_degree(i) (ia[(i) + 1] - ia[(i)])
36
38 /* find the ideal distance between edges, either 1, or |N[i] \Union N[j]| -
39 * |N[i] \Intersection N[j]|
40 */
41 double sum;
42
43 assert(SparseMatrix_is_symmetric(A, false));
44
46 const int *const ia = D->ia;
47 const int *const ja = D->ja;
48 if (D->type != MATRIX_TYPE_REAL) {
49 free(D->a);
50 D->type = MATRIX_TYPE_REAL;
51 D->a = gv_calloc(D->nz, sizeof(double));
52 }
53 double *const d = D->a;
54
55 size_t *const mask = gv_calloc(D->m, sizeof(size_t));
56 for (size_t i = 0; i < D->m; i++)
57 mask[i] = SIZE_MAX;
58
59 for (size_t i = 0; i < D->m; i++) {
60 const double di = node_degree(i);
61 mask[i] = i;
62 for (int j = ia[i]; j < ia[i + 1]; j++) {
63 if ((int)i == ja[j])
64 continue;
65 mask[ja[j]] = i;
66 }
67 for (int j = ia[i]; j < ia[i + 1]; j++) {
68 const int k = ja[j];
69 if ((int)i == k)
70 continue;
71 double len = di + node_degree(k);
72 for (int l = ia[k]; l < ia[k + 1]; l++) {
73 if (mask[ja[l]] == i)
74 len--;
75 }
76 d[j] = len;
77 assert(len > 0);
78 }
79 }
80
81 sum = 0;
82 double sumd = 0;
83 int nz = 0;
84 for (size_t i = 0; i < D->m; i++) {
85 for (int j = ia[i]; j < ia[i + 1]; j++) {
86 if ((int)i == ja[j])
87 continue;
88 nz++;
89 sum += distance(x, dim, (int)i, ja[j]);
90 sumd += d[j];
91 }
92 }
93 sum /= nz;
94 sumd /= nz;
95 sum = sum / sumd;
96
97 for (size_t i = 0; i < D->m; i++) {
98 for (int j = ia[i]; j < ia[i + 1]; j++) {
99 if ((int)i == ja[j])
100 continue;
101 d[j] = sum * d[j];
102 }
103 }
104
105 free(mask);
106 return D;
107}
108
111 double *x, int ideal_dist_scheme) {
112 /* use up to dist 2 neighbor. This is used in overcoming pherical effect with
113 ideal distance of 2-neighbors equal graph distance etc.
114 */
115 int j, k, l, *ia = A->ia, *ja = A->ja, *iw, *jw, *id, *jd;
116 const size_t m = A->m;
117 double *d, *w, *lambda;
118 double diag_d, diag_w, dist, s = 0, stop = 0, sbot = 0;
120
121 assert(SparseMatrix_is_symmetric(A, false));
122
124
127 sm->scaling = 1.;
128 sm->data = NULL;
130 sm->tol_cg = 0.01;
131 sm->maxit_cg = floor(sqrt((double)A->m));
132
133 lambda = sm->lambda = gv_calloc(m, sizeof(double));
134 for (size_t i = 0; i < m; i++)
135 sm->lambda[i] = lambda0;
136 size_t *const mask = gv_calloc(m, sizeof(size_t));
137
138 double *avg_dist = gv_calloc(m, sizeof(double));
139
140 for (size_t i = 0; i < m; i++) {
141 avg_dist[i] = 0;
142 int nz = 0;
143 for (j = ia[i]; j < ia[i + 1]; j++) {
144 if ((int)i == ja[j])
145 continue;
146 avg_dist[i] += distance(x, dim, (int)i, ja[j]);
147 nz++;
148 }
149 assert(nz > 0);
150 avg_dist[i] /= nz;
151 }
152
153 for (size_t i = 0; i < m; i++)
154 mask[i] = SIZE_MAX;
155
156 size_t nz = 0;
157 for (size_t i = 0; i < m; i++) {
158 mask[i] = i;
159 for (j = ia[i]; j < ia[i + 1]; j++) {
160 k = ja[j];
161 if (mask[k] != i) {
162 mask[k] = i;
163 nz++;
164 }
165 }
166 for (j = ia[i]; j < ia[i + 1]; j++) {
167 k = ja[j];
168 for (l = ia[k]; l < ia[k + 1]; l++) {
169 if (mask[ja[l]] != i) {
170 mask[ja[l]] = i;
171 nz++;
172 }
173 }
174 }
175 }
176
177 sm->Lw = SparseMatrix_new(m, (int)m, nz + m, MATRIX_TYPE_REAL, FORMAT_CSR);
178 assert(sm->Lw != NULL);
179 sm->Lwd = SparseMatrix_new(m, (int)m, nz + m, MATRIX_TYPE_REAL, FORMAT_CSR);
180 assert(sm->Lwd != NULL);
181
182 iw = sm->Lw->ia;
183 jw = sm->Lw->ja;
184
185 w = sm->Lw->a;
186 d = sm->Lwd->a;
187
188 id = sm->Lwd->ia;
189 jd = sm->Lwd->ja;
190 iw[0] = id[0] = 0;
191
192 nz = 0;
193 for (size_t i = 0; i < m; i++) {
194 mask[i] = i + m;
195 diag_d = diag_w = 0;
196 for (j = ia[i]; j < ia[i + 1]; j++) {
197 k = ja[j];
198 if (mask[k] != i + m) {
199 mask[k] = i + m;
200
201 jw[nz] = k;
202 if (ideal_dist_scheme == IDEAL_GRAPH_DIST) {
203 dist = 1;
204 } else if (ideal_dist_scheme == IDEAL_AVG_DIST) {
205 dist = (avg_dist[i] + avg_dist[k]) * 0.5;
206 } else if (ideal_dist_scheme == IDEAL_POWER_DIST) {
207 dist = pow(distance_cropped(x, dim, (int)i, k), .4);
208 } else {
209 fprintf(stderr, "ideal_dist_scheme value wrong");
210 assert(0);
211 graphviz_exit(1);
212 }
213
214 /*
215 w[nz] = -1./(ia[i+1]-ia[i]+ia[ja[j]+1]-ia[ja[j]]);
216 w[nz] = -2./(avg_dist[i]+avg_dist[k]);*/
217 /* w[nz] = -1.;*/ /* use unit weight for now, later can try
218 1/(deg(i)+deg(k)) */
219 w[nz] = -1 / (dist * dist);
220
221 diag_w += w[nz];
222
223 jd[nz] = k;
224 /*
225 d[nz] = w[nz]*distance(x,dim,i,k);
226 d[nz] = w[nz]*dd[j];
227 d[nz] = w[nz]*(avg_dist[i] + avg_dist[k])*0.5;
228 */
229 d[nz] = w[nz] * dist;
230 stop += d[nz] * distance(x, dim, (int)i, k);
231 sbot += d[nz] * dist;
232 diag_d += d[nz];
233
234 nz++;
235 }
236 }
237
238 /* distance 2 neighbors */
239 for (j = ia[i]; j < ia[i + 1]; j++) {
240 k = ja[j];
241 for (l = ia[k]; l < ia[k + 1]; l++) {
242 if (mask[ja[l]] != i + m) {
243 mask[ja[l]] = i + m;
244
245 if (ideal_dist_scheme == IDEAL_GRAPH_DIST) {
246 dist = 2;
247 } else if (ideal_dist_scheme == IDEAL_AVG_DIST) {
248 dist = (avg_dist[i] + 2 * avg_dist[k] + avg_dist[ja[l]]) * 0.5;
249 } else if (ideal_dist_scheme == IDEAL_POWER_DIST) {
250 dist = pow(distance_cropped(x, dim, (int)i, ja[l]), .4);
251 } else {
252 fprintf(stderr, "ideal_dist_scheme value wrong");
253 assert(0);
254 graphviz_exit(1);
255 }
256
257 jw[nz] = ja[l];
258 /*
259 w[nz] = -1/(ia[i+1]-ia[i]+ia[ja[l]+1]-ia[ja[l]]);
260 w[nz] = -2/(avg_dist[i] + 2*avg_dist[k] + avg_dist[ja[l]]);*/
261 /* w[nz] = -1.;*/ /* use unit weight for now, later can try
262 1/(deg(i)+deg(k)) */
263
264 w[nz] = -1 / (dist * dist);
265
266 diag_w += w[nz];
267
268 jd[nz] = ja[l];
269 /*
270 d[nz] = w[nz]*(distance(x,dim,i,k)+distance(x,dim,k,ja[l]));
271 d[nz] = w[nz]*(dd[j]+dd[l]);
272 d[nz] = w[nz]*(avg_dist[i] + 2*avg_dist[k] + avg_dist[ja[l]])*0.5;
273 */
274 d[nz] = w[nz] * dist;
275 stop += d[nz] * distance(x, dim, ja[l], k);
276 sbot += d[nz] * dist;
277 diag_d += d[nz];
278
279 nz++;
280 }
281 }
282 }
283 jw[nz] = (int)i;
284 lambda[i] *= (-diag_w); /* alternatively don't do that then we have a
285 constant penalty term scaled by lambda0 */
286
287 w[nz] = -diag_w + lambda[i];
288 jd[nz] = (int)i;
289 d[nz] = -diag_d;
290 nz++;
291
292 assert(nz <= INT_MAX);
293 iw[i + 1] = (int)nz;
294 id[i + 1] = (int)nz;
295 }
296 s = stop / sbot;
297 for (size_t i = 0; i < nz; i++)
298 d[i] *= s;
299
300 sm->scaling = s;
301 sm->Lw->nz = nz;
302 sm->Lwd->nz = nz;
303
304 free(mask);
305 free(avg_dist);
307 return sm;
308}
309
312 /* solve a stress model to achieve the ideal distance among a sparse set of
313 edges recorded in A. A must be a real matrix.
314 */
315 const size_t m = A->m;
316 double stop = 0, sbot = 0;
317
318 assert(SparseMatrix_is_symmetric(A, false) && A->type == MATRIX_TYPE_REAL);
319
320 /* if x is all zero, make it random */
321 bool has_nonzero = false;
322 for (size_t i = 0; i < m * (size_t)dim; i++) {
323 if (!is_exactly_equal(x[i], 0)) {
324 has_nonzero = true;
325 break;
326 }
327 }
328 if (!has_nonzero) {
329 for (size_t i = 0; i < m * (size_t)dim; i++)
330 x[i] = 72 * drand();
331 }
332
333 const int *const ia = A->ia;
334 const int *const ja = A->ja;
335 const double *const a = A->a;
336
339 sm->scaling = 1.;
340 sm->data = NULL;
342 sm->D = A;
343 sm->tol_cg = 0.01;
344 sm->maxit_cg = floor(sqrt((double)A->m));
345
346 double *const lambda = sm->lambda = gv_calloc(m, sizeof(double));
347
348 size_t nz = A->nz;
349
350 sm->Lw = SparseMatrix_new(m, (int)m, nz + m, MATRIX_TYPE_REAL, FORMAT_CSR);
351 assert(sm->Lw != NULL);
352 sm->Lwd = SparseMatrix_new(m, (int)m, nz + 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 (size_t 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 != (int)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, (int)i, k);
379 sbot += d[nz] * dist;
380 diag_d += d[nz];
381
382 nz++;
383 }
384 }
385
386 jw[nz] = (int)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] = (int)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(size_t m, int dim, double *x, double *y) {
415 double total = 0, dist = 0;
416 int j;
417
418 for (size_t i = 0; i < m; i++) {
419 dist = 0.;
420 for (j = 0; j < dim; j++) {
421 dist += (y[(int)i * dim + j] - x[(int)i * dim + j]) *
422 (y[(int)i * dim + j] - x[(int)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, (int)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, (int)m, irn, jcn, val,
554 MATRIX_TYPE_REAL, sizeof(double));
555 }
556 *LL = Lc;
557 *rhs = x00;
558}
559
560static UNUSED double get_stress(size_t m, int dim, int *iw, int *jw, double *w,
561 double *d, double *x, double scaling) {
562 int 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 (size_t i = 0; i < m; i++) {
567 for (j = iw[i]; j < iw[i + 1]; j++) {
568 if ((int)i == jw[j]) {
569 continue;
570 }
571 dist = d[j] / w[j]; /* both negative*/
572 res += -w[j] * (dist - distance(x, dim, (int)i, jw[j])) *
573 (dist - distance(x, dim, (int)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 j, k, *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 const size_t 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 (size_t i = 0; i < m; i++) {
627 idiag = -1;
628 diag = 0.;
629 for (j = id[i]; j < id[i + 1]; j++) {
630 if ((int)i == jd[j]) {
631 idiag = j;
632 continue;
633 }
634
635 dist = distance(x, dim, (int)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, (int)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 (size_t i = 0; i < m; i++) {
659 for (j = 0; j < dim; j++) {
660 y[(int)i * dim + j] += lambda[i] * x0[(int)i * dim + j];
661 }
662 }
663 }
664
665 /* additional term added to the rhs */
666 switch (sm->scheme) {
668 for (size_t i = 0; i < m; i++) {
669 for (j = 0; j < dim; j++) {
670 y[(int)i * dim + j] += x00[(int)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 =
694 total_distance(m, dim, x, y) / sqrt(vector_product((int)m * dim, x, x));
695#ifdef DEBUG_PRINT
696 if (Verbose) {
697 fprintf(stderr,
698 "Outer iter = %d, cg res = %g, ||x_{k+1}-x_k||/||x_k|| = %g\n",
699 iter, res, diff);
700 }
701#endif
702
703 memcpy(x, y, sizeof(double) * m * dim);
704 }
705
706#ifdef DEBUG
707 _statistics[1] += iter - 1;
708#endif
709
710#ifdef DEBUG_PRINT
711 if (Verbose)
712 fprintf(stderr, "iter = %d, final stress = %f\n", iter,
713 get_stress(m, dim, iw, jw, w, d, x, sm->scaling));
714#endif
715
716RETURN:
718 if (Lc) {
721 }
722
723 free(x0);
724 free(y);
725 free(x00);
726 return diff;
727}
728
730 if (!sm)
731 return;
732 if (sm->Lw)
734 if (sm->Lwd)
736 free(sm->lambda);
737 if (sm->data)
738 sm->data_deallocator(sm->data);
739 free(sm);
740}
741
743 bool use_triangularization) {
744 int j, k, *ia = A->ia, *ja = A->ja, *iw, *jw, jdiag, nz;
745 const size_t m = A->m;
747 double *d, *w, diag_d, diag_w, dist;
748 double s = 0, stop = 0, sbot = 0;
749
750 assert(SparseMatrix_is_symmetric(A, false));
751
752 double *avg_dist = gv_calloc(m, sizeof(double));
753
754 for (size_t i = 0; i < m; i++) {
755 avg_dist[i] = 0;
756 nz = 0;
757 for (j = ia[i]; j < ia[i + 1]; j++) {
758 if ((int)i == ja[j])
759 continue;
760 avg_dist[i] += distance(x, dim, (int)i, ja[j]);
761 nz++;
762 }
763 assert(nz > 0);
764 avg_dist[i] /= nz;
765 }
766
768 sm->scaling = 1;
769 sm->data = NULL;
771 sm->tol_cg = 0.01;
772 sm->maxit_cg = floor(sqrt((double)A->m));
773
774 double *lambda = sm->lambda = gv_calloc(m, sizeof(double));
775
776 if (m > 2) {
777 if (use_triangularization) {
778 B = call_tri((int)m, x);
779 } else {
780 B = call_tri2(m, dim, x);
781 }
782 } else {
784 }
785
786 sm->Lw = SparseMatrix_add(A, B);
787
789 sm->Lwd = SparseMatrix_copy(sm->Lw);
790 if (!sm->Lw || !sm->Lwd) {
792 return NULL;
793 }
794
795 iw = sm->Lw->ia;
796 jw = sm->Lw->ja;
797
798 w = sm->Lw->a;
799 d = sm->Lwd->a;
800
801 for (size_t i = 0; i < m; i++) {
802 diag_d = diag_w = 0;
803 jdiag = -1;
804 for (j = iw[i]; j < iw[i + 1]; j++) {
805 k = jw[j];
806 if (k == (int)i) {
807 jdiag = j;
808 continue;
809 }
810 /* w[j] = -1./(ia[i+1]-ia[i]+ia[ja[j]+1]-ia[ja[j]]);
811 w[j] = -2./(avg_dist[i]+avg_dist[k]);
812 w[j] = -1.*/
813 ; /* use unit weight for now, later can try 1/(deg(i)+deg(k)) */
814 dist = pow(distance_cropped(x, dim, (int)i, k), 0.6);
815 w[j] = 1 / (dist * dist);
816 diag_w += w[j];
817
818 /* d[j] = w[j]*distance(x,dim,i,k);
819 d[j] = w[j]*(avg_dist[i] + avg_dist[k])*0.5;*/
820 d[j] = w[j] * dist;
821 stop += d[j] * distance(x, dim, (int)i, k);
822 sbot += d[j] * dist;
823 diag_d += d[j];
824 }
825
826 lambda[i] *= (-diag_w);
827
828 assert(jdiag >= 0);
829 w[jdiag] = -diag_w + lambda[i];
830 d[jdiag] = -diag_d;
831 }
832
833 s = stop / sbot;
834 for (int i = 0; i < iw[m]; i++)
835 d[i] *= s;
836 sm->scaling = s;
837
838 free(avg_dist);
839
840 return sm;
841}
842
847
851
852/* ================================ spring and spring-electrical based smoother
853 * ================ */
855 spring_electrical_control ctrl, double *x) {
856 int j, k, l, *ia = A->ia, *ja = A->ja, *id, *jd;
857 const size_t m = A->m;
858 double *d, *dd;
860
861 assert(SparseMatrix_is_symmetric(A, false));
862
864 dd = ID->a;
865
866 SpringSmoother sm = gv_alloc(sizeof(struct SpringSmoother_struct));
867 size_t *const mask = gv_calloc(m, sizeof(size_t));
868
869 for (size_t i = 0; i < m; i++)
870 mask[i] = SIZE_MAX;
871
872 size_t nz = 0;
873 for (size_t i = 0; i < m; i++) {
874 mask[i] = i;
875 for (j = ia[i]; j < ia[i + 1]; j++) {
876 k = ja[j];
877 if (mask[k] != i) {
878 mask[k] = i;
879 nz++;
880 }
881 }
882 for (j = ia[i]; j < ia[i + 1]; j++) {
883 k = ja[j];
884 for (l = ia[k]; l < ia[k + 1]; l++) {
885 if (mask[ja[l]] != i) {
886 mask[ja[l]] = i;
887 nz++;
888 }
889 }
890 }
891 }
892
893 sm->D = SparseMatrix_new(m, (int)m, nz, MATRIX_TYPE_REAL, FORMAT_CSR);
894 assert(sm->D != NULL);
895
896 id = sm->D->ia;
897 jd = sm->D->ja;
898 d = sm->D->a;
899 id[0] = 0;
900
901 nz = 0;
902 for (size_t i = 0; i < m; i++) {
903 mask[i] = i + m;
904 for (j = ia[i]; j < ia[i + 1]; j++) {
905 k = ja[j];
906 if (mask[k] != i + m) {
907 mask[k] = i + m;
908 jd[nz] = k;
909 d[nz] = dd[j];
910 nz++;
911 }
912 }
913
914 for (j = ia[i]; j < ia[i + 1]; j++) {
915 k = ja[j];
916 for (l = ia[k]; l < ia[k + 1]; l++) {
917 if (mask[ja[l]] != i + m) {
918 mask[ja[l]] = i + m;
919 jd[nz] = ja[l];
920 d[nz] = dd[j] + dd[l];
921 nz++;
922 }
923 }
924 }
925 assert(nz <= INT_MAX);
926 id[i + 1] = (int)nz;
927 }
928 sm->D->nz = nz;
929 sm->ctrl = ctrl;
930 sm->ctrl.random_start = false;
931 sm->ctrl.multilevels = 1;
932 sm->ctrl.step /= 2;
933 sm->ctrl.maxiter = 20;
934
935 free(mask);
937
938 return sm;
939}
940
942 if (!sm)
943 return;
944 if (sm->D)
946 free(sm);
947}
948
950 double *x) {
952}
953
954/*=============================== end of spring and spring-electrical based
955 * smoother =========== */
956
958 spring_electrical_control ctrl, double *x) {
959#ifdef TIME
960 clock_t cpu;
961#endif
962
963#ifdef TIME
964 cpu = clock();
965#endif
966
967 switch (ctrl.smoothing) {
968 case SMOOTHING_RNG:
969 case SMOOTHING_TRIANGLE: {
971
972 if (A->m > 2) { /* triangles need at least 3 nodes */
973 if (ctrl.smoothing == SMOOTHING_RNG) {
974 sm = TriangleSmoother_new(A, dim, x, false);
975 } else {
976 sm = TriangleSmoother_new(A, dim, x, true);
977 }
980 }
981 break;
982 }
987 int dist_scheme = IDEAL_AVG_DIST;
988
990 dist_scheme = IDEAL_GRAPH_DIST;
992 dist_scheme = IDEAL_AVG_DIST;
994 dist_scheme = IDEAL_POWER_DIST;
995 }
996
997 sm = StressMajorizationSmoother2_new(A, dim, 0.05, x, dist_scheme);
1000 break;
1001 }
1002 case SMOOTHING_SPRING: {
1003 SpringSmoother sm = SpringSmoother_new(A, dim, ctrl, x);
1004 SpringSmoother_smooth(sm, A, dim, x);
1006
1007 break;
1008 }
1009
1010 } /* end switch between smoothing methods */
1011
1012#ifdef TIME
1013 if (Verbose)
1014 fprintf(stderr, "post processing %f\n",
1015 ((double)(clock() - cpu)) / CLOCKS_PER_SEC);
1016#endif
1017}
SparseMatrix SparseMatrix_new(size_t m, int n, size_t nz, int type, int format)
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, size_t 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)
@ 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(size_t 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:116
double vector_product(int n, double *x, double *y)
Definition general.c:33
double distance_cropped(double *x, int dim, int i, int j)
Definition general.c:111
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
#define SIZE_MAX
Definition gmlscan.c:347
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:51
#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
StressMajorizationSmoother SparseStressMajorizationSmoother_new(SparseMatrix A, int dim, double *x)
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)
static double total_distance(size_t m, int dim, double *x, double *y)
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)
static UNUSED double get_stress(size_t m, int dim, int *iw, int *jw, double *w, double *d, double *x, double scaling)
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 void get_edge_label_matrix(relative_position_constraints data, size_t m, int dim, double *x, SparseMatrix *LL, double **rhs)
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)
void spring_electrical_spring_embedding(int dim, SparseMatrix A0, SparseMatrix D, spring_electrical_control *ctrl, double *x)
static const double tol
@ 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 m
row dimension
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