Graphviz 14.0.3~dev.20251104.0241
Loading...
Searching...
No Matches
stress.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 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 <float.h>
12#include <neatogen/neato.h>
13#include <neatogen/dijkstra.h>
14#include <neatogen/bfs.h>
15#include <neatogen/pca.h>
16#include <neatogen/matrix_ops.h>
17#include <neatogen/conjgrad.h>
19#include <neatogen/kkutils.h>
20#include <neatogen/stress.h>
21#include <math.h>
22#include <stdbool.h>
23#include <stdlib.h>
24#include <time.h>
25#include <util/alloc.h>
26
27// the terms in the stress energy are normalized by dᵢⱼ¯²
28
29/* dimensionality of subspace; relevant
30 * when optimizing within subspace)
31 */
32#define stress_pca_dim 50
33
34 /* a structure used for storing sparse distance matrix */
35typedef struct {
36 size_t nedges;
37 int *edges;
40} dist_data;
41
42static double compute_stressf(float **coords, float *lap, int dim, int n, int exp)
43{
44 /* compute the overall stress */
45
46 int i, j, l, neighbor, count;
47 double sum, dist, Dij;
48 sum = 0;
49 for (count = 0, i = 0; i < n - 1; i++) {
50 count++; /* skip diagonal entry */
51 for (j = 1; j < n - i; j++, count++) {
52 dist = 0;
53 neighbor = i + j;
54 for (l = 0; l < dim; l++) {
55 dist +=
56 (coords[l][i] - coords[l][neighbor]) * (coords[l][i] -
57 coords[l]
58 [neighbor]);
59 }
60 dist = sqrt(dist);
61 if (exp == 2) {
62 Dij = 1.0 / sqrt(lap[count]);
63 sum += (Dij - dist) * (Dij - dist) * lap[count];
64 } else {
65 Dij = 1.0 / lap[count];
66 sum += (Dij - dist) * (Dij - dist) * lap[count];
67 }
68 }
69 }
70
71 return sum;
72}
73
74static double
75compute_stress1(double **coords, dist_data * distances, int dim, int n, int exp)
76{
77 /* compute the overall stress */
78
79 int i, l, node;
80 double sum, dist, Dij;
81 sum = 0;
82 if (exp == 2) {
83 for (i = 0; i < n; i++) {
84 for (size_t j = 0; j < distances[i].nedges; j++) {
85 node = distances[i].edges[j];
86 if (node <= i) {
87 continue;
88 }
89 dist = 0;
90 for (l = 0; l < dim; l++) {
91 dist +=
92 (coords[l][i] - coords[l][node]) * (coords[l][i] -
93 coords[l]
94 [node]);
95 }
96 dist = sqrt(dist);
97 Dij = distances[i].edist[j];
98 sum += (Dij - dist) * (Dij - dist) / (Dij * Dij);
99 }
100 }
101 } else {
102 for (i = 0; i < n; i++) {
103 for (size_t j = 0; j < distances[i].nedges; j++) {
104 node = distances[i].edges[j];
105 if (node <= i) {
106 continue;
107 }
108 dist = 0;
109 for (l = 0; l < dim; l++) {
110 dist +=
111 (coords[l][i] - coords[l][node]) * (coords[l][i] -
112 coords[l]
113 [node]);
114 }
115 dist = sqrt(dist);
116 Dij = distances[i].edist[j];
117 sum += (Dij - dist) * (Dij - dist) / Dij;
118 }
119 }
120 }
121
122 return sum;
123}
124
125/* Initialize node coordinates. If the node already has
126 * a position, use it.
127 * Return true if some node is fixed.
128 */
129int initLayout(int n, int dim, double **coords, node_t **nodes) {
130 node_t *np;
131 double *xp;
132 double *yp;
133 double *pt;
134 int i, d;
135 int pinned = 0;
136
137 xp = coords[0];
138 yp = coords[1];
139 for (i = 0; i < n; i++) {
140 np = nodes[i];
141 if (hasPos(np)) {
142 pt = ND_pos(np);
143 *xp++ = *pt++;
144 *yp++ = *pt++;
145 if (dim > 2) {
146 for (d = 2; d < dim; d++)
147 coords[d][i] = *pt++;
148 }
149 if (isFixed(np))
150 pinned = 1;
151 } else {
152 *xp++ = drand48();
153 *yp++ = drand48();
154 if (dim > 2) {
155 for (d = 2; d < dim; d++)
156 coords[d][i] = drand48();
157 }
158 }
159 }
160
161 for (d = 0; d < dim; d++)
162 orthog1(n, coords[d]);
163
164 return pinned;
165}
166
167float *circuitModel(vtx_data * graph, int nG)
168{
169 int i, j, rv, count;
170 float *Dij = gv_calloc(nG * (nG + 1) / 2, sizeof(float));
171 double **Gm;
172 double **Gm_inv;
173
174 Gm = new_array(nG, nG, 0.0);
175 Gm_inv = new_array(nG, nG, 0.0);
176
177 /* set non-diagonal entries */
178 if (graph->ewgts) {
179 for (i = 0; i < nG; i++) {
180 for (size_t e = 1; e < graph[i].nedges; e++) {
181 j = graph[i].edges[e];
182 /* conductance is 1/resistance */
183 Gm[i][j] = Gm[j][i] = -1.0 / graph[i].ewgts[e]; /* negate */
184 }
185 }
186 } else {
187 for (i = 0; i < nG; i++) {
188 for (size_t e = 1; e < graph[i].nedges; e++) {
189 j = graph[i].edges[e];
190 /* conductance is 1/resistance */
191 Gm[i][j] = Gm[j][i] = -1.0; /* ewgts are all 1 */
192 }
193 }
194 }
195
196 rv = solveCircuit(nG, Gm, Gm_inv);
197
198 if (rv) {
199 float v;
200 count = 0;
201 for (i = 0; i < nG; i++) {
202 for (j = i; j < nG; j++) {
203 if (i == j)
204 v = 0.0;
205 else
206 v = (float) (Gm_inv[i][i] + Gm_inv[j][j] -
207 2.0 * Gm_inv[i][j]);
208 Dij[count++] = v;
209 }
210 }
211 } else {
212 free(Dij);
213 Dij = NULL;
214 }
215 free_array(Gm);
216 free_array(Gm_inv);
217 return Dij;
218}
219
220/* Optimization of the stress function using sparse distance matrix, within a vector-space
221 * Fastest and least accurate method
222 *
223 * NOTE: We use integral shortest path values here, assuming
224 * this is only to get an initial layout. In general, if edge lengths
225 * are involved, we may end up with 0 length edges.
226 */
227static int sparse_stress_subspace_majorization_kD(vtx_data * graph, /* Input graph in sparse representation */
228 int n, /* Number of nodes */
229 double **coords, /* coordinates of nodes (output layout) */
230 int dim, /* dimensionality of layout */
231 int smart_ini, /* smart initialization */
232 int exp, /* scale exponent */
233 int reweight_graph, /* difference model */
234 int n_iterations, /* max #iterations */
235 int num_centers /* #pivots in sparse distance matrix */
236 )
237{
238 int iterations; /* output: number of iteration of the process */
239
240 double conj_tol = tolerance_cg; /* tolerance of Conjugate Gradient */
241
242 /*************************************************
243 ** Computation of pivot-based, sparse, subspace-restricted **
244 ** k-D stress minimization by majorization **
245 *************************************************/
246
247 int i, k, node;
248
249 /*************************************************
250 ** First compute the subspace in which we optimize **
251 ** The subspace is the high-dimensional embedding **
252 *************************************************/
253
254 int subspace_dim = MIN(stress_pca_dim, n); /* overall dimensionality of subspace */
255 double **subspace = gv_calloc(subspace_dim, sizeof(double *));
256 double *d_storage = gv_calloc(subspace_dim * n, sizeof(double));
257 int num_centers_local;
258 DistType **full_coords;
259 /* if i is a pivot than CenterIndex[i] is its index, otherwise CenterIndex[i]= -1 */
260 int *invCenterIndex; /* list the pivot nodes */
261 float *old_weights;
262 /* this matrix stores the distance between each node and each "center" */
263 DistType **Dij;
264 /* this vector stores the distances of each node to the selected "centers" */
265 DistType max_dist;
266 DistType *storage;
267 int *visited_nodes;
268 dist_data *distances;
269 int available_space;
270 int *storage1 = NULL;
271 DistType *storage2 = NULL;
272 int num_visited_nodes;
273 int num_neighbors;
274 int index;
275 DistType *dist_list;
276 vtx_data *lap;
277 int *edges;
278 float *ewgts;
279 double degree;
280 double **directions;
281 float **tmp_mat;
282 float **matrix;
283 double dist_ij;
284 double *b;
285 double *b_restricted;
286 double L_ij;
287 double old_stress, new_stress;
288 bool converged;
289
290 for (i = 0; i < subspace_dim; i++) {
291 subspace[i] = d_storage + i * n;
292 }
293
294 /* compute PHDE: */
295 num_centers_local = MIN(n, MAX(2 * subspace_dim, 50));
296 full_coords = NULL;
297 /* High dimensional embedding */
298 embed_graph(graph, n, num_centers_local, &full_coords, reweight_graph);
299 /* Centering coordinates */
300 center_coordinate(full_coords, n, num_centers_local);
301 /* PCA */
302 PCA_alloc(full_coords, num_centers_local, n, subspace, subspace_dim);
303
304 free(full_coords[0]);
305 free(full_coords);
306
307 /*************************************************
308 ** Compute the sparse-shortest-distances matrix 'distances' **
309 *************************************************/
310
311 int *CenterIndex = gv_calloc(n, sizeof(int));
312 for (i = 0; i < n; i++) {
313 CenterIndex[i] = -1;
314 }
315 invCenterIndex = NULL;
316
317 old_weights = graph[0].ewgts;
318
319 if (reweight_graph) {
320 /* weight graph to separate high-degree nodes */
321 /* in the future, perform slower Dijkstra-based computation */
323 }
324
325 /* compute sparse distance matrix */
326 /* first select 'num_centers' pivots from which we compute distance */
327 /* to all other nodes */
328
329 Dij = NULL;
330 DistType *dist = gv_calloc(n, sizeof(DistType));
331 if (num_centers == 0) { /* no pivots, skip pivots-to-nodes distance calculation */
332 goto after_pivots_selection;
333 }
334
335 invCenterIndex = gv_calloc(num_centers, sizeof(int));
336
337 storage = gv_calloc(n * num_centers, sizeof(DistType));
338 Dij = gv_calloc(num_centers, sizeof(DistType *));
339 for (i = 0; i < num_centers; i++)
340 Dij[i] = storage + i * n;
341
342 // select `num_centers` pivots that are uniformly spread over the graph
343
344 /* the first pivots is selected randomly */
345 node = rand() % n;
346 CenterIndex[node] = 0;
347 invCenterIndex[0] = node;
348
349 if (reweight_graph) {
350 ngdijkstra(node, graph, n, Dij[0]);
351 } else {
352 bfs(node, graph, n, Dij[0]);
353 }
354
355 /* find the most distant node from first pivot */
356 max_dist = 0;
357 for (i = 0; i < n; i++) {
358 dist[i] = Dij[0][i];
359 if (dist[i] > max_dist) {
360 node = i;
361 max_dist = dist[i];
362 }
363 }
364 /* select other dim-1 nodes as pivots */
365 for (i = 1; i < num_centers; i++) {
366 CenterIndex[node] = i;
367 invCenterIndex[i] = node;
368 if (reweight_graph) {
369 ngdijkstra(node, graph, n, Dij[i]);
370 } else {
371 bfs(node, graph, n, Dij[i]);
372 }
373 max_dist = 0;
374 for (int j = 0; j < n; j++) {
375 dist[j] = MIN(dist[j], Dij[i][j]);
376 if (dist[j] > max_dist
377 || (dist[j] == max_dist && rand() % (j + 1) == 0)) {
378 node = j;
379 max_dist = dist[j];
380 }
381 }
382 }
383
384 after_pivots_selection:
385
386 /* Construct a sparse distance matrix 'distances' */
387
388 /* initialize dist to -1, important for 'bfs_bounded(..)' */
389 for (i = 0; i < n; i++) {
390 dist[i] = -1;
391 }
392
393 visited_nodes = gv_calloc(n, sizeof(int));
394 distances = gv_calloc(n, sizeof(dist_data));
395 available_space = 0;
396 size_t nedges = 0;
397 for (i = 0; i < n; i++) {
398 if (CenterIndex[i] >= 0) { /* a pivot node */
399 distances[i].edges = gv_calloc(n - 1, sizeof(int));
400 distances[i].edist = gv_calloc(n - 1, sizeof(DistType));
401 distances[i].nedges = (size_t)n - 1;
402 nedges += (size_t)n - 1;
403 distances[i].free_mem = true;
404 index = CenterIndex[i];
405 for (int j = 0; j < i; j++) {
406 distances[i].edges[j] = j;
407 distances[i].edist[j] = Dij[index][j];
408 }
409 for (int j = i + 1; j < n; j++) {
410 distances[i].edges[j - 1] = j;
411 distances[i].edist[j - 1] = Dij[index][j];
412 }
413 continue;
414 }
415
416 /* a non pivot node */
417
418 num_visited_nodes = 0;
419 num_neighbors = num_visited_nodes + num_centers;
420 if (num_neighbors > available_space) {
421 available_space = n;
422 storage1 = gv_calloc(available_space, sizeof(int));
423 storage2 = gv_calloc(available_space, sizeof(DistType));
424 distances[i].free_mem = true;
425 } else {
426 distances[i].free_mem = false;
427 }
428 distances[i].edges = storage1;
429 distances[i].edist = storage2;
430 distances[i].nedges = (size_t)num_neighbors;
431 nedges += (size_t)num_neighbors;
432 for (int j = 0; j < num_visited_nodes; j++) {
433 storage1[j] = visited_nodes[j];
434 storage2[j] = dist[visited_nodes[j]];
435 dist[visited_nodes[j]] = -1;
436 }
437 /* add all pivots: */
438 for (int j = num_visited_nodes; j < num_neighbors; j++) {
439 index = j - num_visited_nodes;
440 storage1[j] = invCenterIndex[index];
441 storage2[j] = Dij[index][i];
442 }
443
444 storage1 += num_neighbors;
445 storage2 += num_neighbors;
446 available_space -= num_neighbors;
447 }
448
449 free(dist);
450 free(visited_nodes);
451
452 if (Dij != NULL) {
453 free(Dij[0]);
454 free(Dij);
455 }
456
457 /*************************************************
458 ** Laplacian computation **
459 *************************************************/
460
461 lap = gv_calloc(n, sizeof(vtx_data));
462 edges = gv_calloc(nedges + n, sizeof(int));
463 ewgts = gv_calloc(nedges + n, sizeof(float));
464 for (i = 0; i < n; i++) {
465 lap[i].edges = edges;
466 lap[i].ewgts = ewgts;
467 lap[i].nedges = distances[i].nedges + 1; /*add the self loop */
468 dist_list = distances[i].edist - 1; /* '-1' since edist[0] goes for number '1' entry in the lap */
469 degree = 0;
470 if (exp == 2) {
471 for (size_t j = 1; j < lap[i].nedges; j++) {
472 edges[j] = distances[i].edges[j - 1];
473 ewgts[j] = (float) -1.0 / ((float) dist_list[j] * (float) dist_list[j]); /* cast to float to prevent overflow */
474 degree -= ewgts[j];
475 }
476 } else {
477 for (size_t j = 1; j < lap[i].nedges; j++) {
478 edges[j] = distances[i].edges[j - 1];
479 ewgts[j] = -1.0f / (float) dist_list[j];
480 degree -= ewgts[j];
481 }
482 }
483 edges[0] = i;
484 ewgts[0] = (float) degree;
485 edges += lap[i].nedges;
486 ewgts += lap[i].nedges;
487 }
488
489 /*************************************************
490 ** initialize direction vectors **
491 ** to get an initial layout **
492 *************************************************/
493
494 /* the layout is subspace*directions */
495 directions = gv_calloc(dim, sizeof(double *));
496 directions[0] = gv_calloc(dim * subspace_dim, sizeof(double));
497 for (i = 1; i < dim; i++) {
498 directions[i] = directions[0] + i * subspace_dim;
499 }
500
501 if (smart_ini) {
502 /* smart initialization */
503 for (k = 0; k < dim; k++) {
504 for (i = 0; i < subspace_dim; i++) {
505 directions[k][i] = 0;
506 }
507 }
508 if (dim != 2) {
509 /* use the first vectors in the eigenspace */
510 /* each direction points to its "principal axes" */
511 for (k = 0; k < dim; k++) {
512 directions[k][k] = 1;
513 }
514 } else {
515 /* for the frequent 2-D case we prefer iterative-PCA over PCA */
516 /* Note that we don't want to mix the Lap's eigenspace with the HDE */
517 /* in the computation since they have different scales */
518
519 directions[0][0] = 1; /* first pca projection vector */
520 if (!iterativePCA_1D(subspace, subspace_dim, n, directions[1])) {
521 for (k = 0; k < subspace_dim; k++) {
522 directions[1][k] = 0;
523 }
524 directions[1][1] = 1;
525 }
526 }
527
528 } else {
529 /* random initialization */
530 for (k = 0; k < dim; k++) {
531 for (i = 0; i < subspace_dim; i++) {
532 directions[k][i] = (double) rand() / RAND_MAX;
533 }
534 }
535 }
536
537 /* compute initial k-D layout */
538
539 for (k = 0; k < dim; k++) {
540 right_mult_with_vector_transpose(subspace, n, subspace_dim,
541 directions[k], coords[k]);
542 }
543
544 /*************************************************
545 ** compute restriction of the laplacian to subspace: **
546 *************************************************/
547
548 tmp_mat = NULL;
549 matrix = NULL;
550 mult_sparse_dense_mat_transpose(lap, subspace, n, subspace_dim,
551 &tmp_mat);
552 mult_dense_mat(subspace, tmp_mat, subspace_dim, n, subspace_dim,
553 &matrix);
554 free(tmp_mat[0]);
555 free(tmp_mat);
556
557 /*************************************************
558 ** Layout optimization **
559 *************************************************/
560
561 b = gv_calloc(n, sizeof(double));
562 b_restricted = gv_calloc(subspace_dim, sizeof(double));
563 old_stress = compute_stress1(coords, distances, dim, n, exp);
564 for (converged = false, iterations = 0;
565 iterations < n_iterations && !converged; iterations++) {
566
567 /* Axis-by-axis optimization: */
568 for (k = 0; k < dim; k++) {
569 /* compute the vector b */
570 /* multiply on-the-fly with distance-based laplacian */
571 /* (for saving storage we don't construct this Lap explicitly) */
572 for (i = 0; i < n; i++) {
573 degree = 0;
574 b[i] = 0;
575 dist_list = distances[i].edist - 1;
576 edges = lap[i].edges;
577 ewgts = lap[i].ewgts;
578 for (size_t j = 1; j < lap[i].nedges; j++) {
579 node = edges[j];
580 dist_ij = distance_kD(coords, dim, i, node);
581 if (dist_ij > 1e-30) { /* skip zero distances */
582 L_ij = -ewgts[j] * dist_list[j] / dist_ij; /* L_ij=w_{ij}*d_{ij}/dist_{ij} */
583 degree -= L_ij;
584 b[i] += L_ij * coords[k][node];
585 }
586 }
587 b[i] += degree * coords[k][i];
588 }
589 right_mult_with_vector_d(subspace, subspace_dim, n, b,
590 b_restricted);
591 if (conjugate_gradient_f(matrix, directions[k], b_restricted,
592 subspace_dim, conj_tol, subspace_dim,
593 false)) {
594 iterations = -1;
595 goto finish0;
596 }
597 right_mult_with_vector_transpose(subspace, n, subspace_dim,
598 directions[k], coords[k]);
599 }
600
601 if (iterations % 2 == 0) { // check for convergence each two iterations
602 new_stress = compute_stress1(coords, distances, dim, n, exp);
603 converged = fabs(new_stress - old_stress) / (new_stress + 1e-10) < Epsilon;
604 old_stress = new_stress;
605 }
606 }
607finish0:
608 free(b_restricted);
609 free(b);
610
611 if (reweight_graph) {
612 restore_old_weights(graph, n, old_weights);
613 }
614
615 for (i = 0; i < n; i++) {
616 if (distances[i].free_mem) {
617 free(distances[i].edges);
618 free(distances[i].edist);
619 }
620 }
621
622 free(distances);
623 free(lap[0].edges);
624 free(lap[0].ewgts);
625 free(lap);
626 free(CenterIndex);
627 free(invCenterIndex);
628 free(directions[0]);
629 free(directions);
630 if (matrix != NULL) {
631 free(matrix[0]);
632 free(matrix);
633 }
634 free(subspace[0]);
635 free(subspace);
636
637 return iterations;
638}
639
640/* compute_weighted_apsp_packed:
641 * Edge lengths can be any float > 0
642 */
644{
645 int i, j, count;
646 float *Dij = gv_calloc(n * (n + 1) / 2, sizeof(float));
647
648 float *Di = gv_calloc(n, sizeof(float));
649
650 count = 0;
651 for (i = 0; i < n; i++) {
652 dijkstra_f(i, graph, n, Di);
653 for (j = i; j < n; j++) {
654 Dij[count++] = Di[j];
655 }
656 }
657 free(Di);
658 return Dij;
659}
660
661
663float *mdsModel(vtx_data * graph, int nG)
664{
665 int i, j;
666 float *Dij;
667 int shift = 0;
668 double delta = 0.0;
669
670 if (graph->ewgts == NULL)
671 return 0;
672
673 /* first, compute shortest paths to fill in non-edges */
675
676 /* then, replace edge entries will user-supplied len */
677 for (i = 0; i < nG; i++) {
678 shift += i;
679 for (size_t e = 1; e < graph[i].nedges; e++) {
680 j = graph[i].edges[e];
681 if (j < i)
682 continue;
683 delta += fabsf(Dij[i * nG + j - shift] - graph[i].ewgts[e]);
684 Dij[i * nG + j - shift] = graph[i].ewgts[e];
685 }
686 }
687 if (Verbose) {
688 fprintf(stderr, "mdsModel: delta = %f\n", delta);
689 }
690 return Dij;
691}
692
695{
696 int i, j, count;
697 float *Dij = gv_calloc(n * (n + 1) / 2, sizeof(float));
698
699 DistType *Di = gv_calloc(n, sizeof(DistType));
700
701 count = 0;
702 for (i = 0; i < n; i++) {
703 bfs(i, graph, n, Di);
704 for (j = i; j < n; j++) {
705 Dij[count++] = (float)Di[j];
706 }
707 }
708 free(Di);
709 return Dij;
710}
711
713 /* compute all-pairs-shortest-path-length while weighting the graph */
714 /* so high-degree nodes are distantly located */
715
716 float *Dij;
717 int i;
718 float *old_weights = graph[0].ewgts;
719 size_t nedges = 0;
720 size_t deg_i, deg_j;
721 int neighbor;
722
723 for (i = 0; i < n; i++) {
724 nedges += graph[i].nedges;
725 }
726
727 float *weights = gv_calloc(nedges, sizeof(float));
728 int *vtx_vec = gv_calloc(n, sizeof(int));
729
730 if (graph->ewgts) {
731 for (i = 0; i < n; i++) {
733 deg_i = graph[i].nedges - 1;
734 for (size_t j = 1; j <= deg_i; j++) {
735 neighbor = graph[i].edges[j];
736 deg_j = graph[neighbor].nedges - 1;
737 weights[j] = fmaxf((float)(deg_i + deg_j -
738 2 * common_neighbors(graph, neighbor, vtx_vec)), graph[i].ewgts[j]);
739 }
740 empty_neighbors_vec(graph, i, vtx_vec);
741 graph[i].ewgts = weights;
742 weights += graph[i].nedges;
743 }
745 } else {
746 for (i = 0; i < n; i++) {
747 graph[i].ewgts = weights;
749 deg_i = graph[i].nedges - 1;
750 for (size_t j = 1; j <= deg_i; j++) {
751 neighbor = graph[i].edges[j];
752 deg_j = graph[neighbor].nedges - 1;
753 weights[j] =
754 (float)(deg_i + deg_j - 2 * common_neighbors(graph, neighbor, vtx_vec));
755 }
756 empty_neighbors_vec(graph, i, vtx_vec);
757 weights += graph[i].nedges;
758 }
759 Dij = compute_apsp_packed(graph, n);
760 }
761
762 free(vtx_vec);
763 free(graph[0].ewgts);
764 graph[0].ewgts = NULL;
765 if (old_weights != NULL) {
766 for (i = 0; i < n; i++) {
767 graph[i].ewgts = old_weights;
768 old_weights += graph[i].nedges;
769 }
770 }
771 return Dij;
772}
773
774#if defined(DEBUG) && DEBUG > 1
775static void dumpMatrix(float *Dij, int n)
776{
777 int i, j, count = 0;
778 for (i = 0; i < n; i++) {
779 for (j = i; j < n; j++) {
780 fprintf(stderr, "%.02f ", Dij[count++]);
781 }
782 fputs("\n", stderr);
783 }
784}
785#endif
786
787/* Accumulator type for diagonal of Laplacian. Needs to be as large
788 * as possible. Use long double; configure to double if necessary.
789 */
790#define DegType long double
791
793int stress_majorization_kD_mkernel(vtx_data * graph, /* Input graph in sparse representation */
794 int n, /* Number of nodes */
795 double **d_coords, /* coordinates of nodes (output layout) */
796 node_t ** nodes, /* original nodes */
797 int dim, /* dimensionality of layout */
798 int opts, /* options */
799 int model, /* model */
800 int maxi /* max iterations */
801 )
802{
803 int iterations; /* output: number of iteration of the process */
804
805 double conj_tol = tolerance_cg; /* tolerance of Conjugate Gradient */
806 float *Dij = NULL;
807 int i, j, k;
808 float **coords = NULL;
809 float *f_storage = NULL;
810 float constant_term;
811 int count;
812 DegType degree;
813 int lap_length;
814 float *lap2 = NULL;
815 DegType *degrees = NULL;
816 int step;
817 float val;
818 double old_stress, new_stress;
819 bool converged;
820 float **b = NULL;
821 float *tmp_coords = NULL;
822 float *dist_accumulator = NULL;
823 float *lap1 = NULL;
824 int smart_ini = opts & opt_smart_init;
825 int exp = opts & opt_exp_flag;
826 int len;
827 int havePinned; /* some node is pinned */
828
829 /*************************************************
830 ** Computation of full, dense, unrestricted k-D **
831 ** stress minimization by majorization **
832 *************************************************/
833
834 /****************************************************
835 ** Compute the all-pairs-shortest-distances matrix **
836 ****************************************************/
837
838 if (maxi < 0)
839 return 0;
840
841 if (Verbose)
842 start_timer();
843
844 if (model == MODEL_SUBSET) {
845 /* weight graph to separate high-degree nodes */
846 /* and perform slower Dijkstra-based computation */
847 if (Verbose)
848 fprintf(stderr, "Calculating subset model");
850 } else if (model == MODEL_CIRCUIT) {
851 Dij = circuitModel(graph, n);
852 if (!Dij) {
854 "graph is disconnected. Hence, the circuit model\n");
856 "is undefined. Reverting to the shortest path model.\n");
857 }
858 } else if (model == MODEL_MDS) {
859 if (Verbose)
860 fprintf(stderr, "Calculating MDS model");
861 Dij = mdsModel(graph, n);
862 }
863 if (!Dij) {
864 if (Verbose)
865 fprintf(stderr, "Calculating shortest paths");
866 if (graph->ewgts)
868 else
869 Dij = compute_apsp_packed(graph, n);
870 }
871
872 if (Verbose) {
873 fprintf(stderr, ": %.2f sec\n", elapsed_sec());
874 fprintf(stderr, "Setting initial positions");
875 start_timer();
876 }
877
878 /**************************
879 ** Layout initialization **
880 **************************/
881
882 if (smart_ini && n > 1) {
883 havePinned = 0;
884 /* optimize layout quickly within subspace */
885 /* perform at most 50 iterations within 30-D subspace to
886 get an estimate */
888 d_coords, dim, smart_ini, exp,
889 model == MODEL_SUBSET, 50,
890 num_pivots_stress) < 0) {
891 iterations = -1;
892 goto finish1;
893 }
894
895 for (i = 0; i < dim; i++) {
896 /* for numerical stability, scale down layout */
897 double max = 1;
898 for (j = 0; j < n; j++) {
899 if (fabs(d_coords[i][j]) > max) {
900 max = fabs(d_coords[i][j]);
901 }
902 }
903 for (j = 0; j < n; j++) {
904 d_coords[i][j] /= max;
905 }
906 /* add small random noise */
907 for (j = 0; j < n; j++) {
908 d_coords[i][j] += 1e-6 * (drand48() - 0.5);
909 }
910 orthog1(n, d_coords[i]);
911 }
912 } else {
913 havePinned = initLayout(n, dim, d_coords, nodes);
914 }
915 if (Verbose)
916 fprintf(stderr, ": %.2f sec", elapsed_sec());
917 if (n == 1 || maxi == 0) {
918 free(Dij);
919 return 0;
920 }
921
922 if (Verbose) {
923 fprintf(stderr, ": %.2f sec\n", elapsed_sec());
924 fprintf(stderr, "Setting up stress function");
925 start_timer();
926 }
927 coords = gv_calloc(dim, sizeof(float *));
928 f_storage = gv_calloc(dim * n, sizeof(float));
929 for (i = 0; i < dim; i++) {
930 coords[i] = f_storage + i * n;
931 for (j = 0; j < n; j++) {
932 coords[i][j] = (float)d_coords[i][j];
933 }
934 }
935
936 /* compute constant term in stress sum */
937 /* which is \sum_{i<j} w_{ij}d_{ij}^2 */
938 assert(exp == 1 || exp == 2);
939 constant_term = (float)n * (n - 1) / 2;
940
941 /**************************
942 ** Laplacian computation **
943 **************************/
944
945 lap_length = n * (n + 1) / 2;
946 lap2 = Dij;
947 if (exp == 2) {
948 square_vec(lap_length, lap2);
949 }
950 /* compute off-diagonal entries */
951 invert_vec(lap_length, lap2);
952
953 /* compute diagonal entries */
954 count = 0;
955 degrees = gv_calloc(n, sizeof(DegType));
956 for (i = 0; i < n - 1; i++) {
957 degree = 0;
958 count++; /* skip main diag entry */
959 for (j = 1; j < n - i; j++, count++) {
960 val = lap2[count];
961 degree += val;
962 degrees[i + j] -= val;
963 }
964 degrees[i] -= degree;
965 }
966 for (step = n, count = 0, i = 0; i < n; i++, count += step, step--) {
967 lap2[count] = degrees[i];
968 }
969
970 /*************************
971 ** Layout optimization **
972 *************************/
973
974 b = gv_calloc(dim, sizeof(float *));
975 b[0] = gv_calloc(dim * n, sizeof(float));
976 for (k = 1; k < dim; k++) {
977 b[k] = b[0] + k * n;
978 }
979
980 tmp_coords = gv_calloc(n, sizeof(float));
981 dist_accumulator = gv_calloc(n, sizeof(float));
982 lap1 = gv_calloc(lap_length, sizeof(float));
983
984
985 old_stress = DBL_MAX; // at least one iteration
986 if (Verbose) {
987 fprintf(stderr, ": %.2f sec\n", elapsed_sec());
988 fprintf(stderr, "Solving model: ");
989 start_timer();
990 }
991
992 for (converged = false, iterations = 0;
993 iterations < maxi && !converged; iterations++) {
994
995 /* First, construct Laplacian of 1/(d_ij*|p_i-p_j|) */
996 /* set_vector_val(n, 0, degrees); */
997 memset(degrees, 0, n * sizeof(DegType));
998 if (exp == 2) {
999 sqrt_vecf(lap_length, lap2, lap1);
1000 }
1001 for (count = 0, i = 0; i < n - 1; i++) {
1002 len = n - i - 1;
1003 /* init 'dist_accumulator' with zeros */
1004 set_vector_valf(len, 0, dist_accumulator);
1005
1006 /* put into 'dist_accumulator' all squared distances between 'i' and 'i'+1,...,'n'-1 */
1007 for (k = 0; k < dim; k++) {
1008 size_t x;
1009 for (x = 0; x < (size_t)len; ++x) {
1010 float tmp = coords[k][i] + -1.0f * (coords[k] + i + 1)[x];
1011 dist_accumulator[x] += tmp * tmp;
1012 }
1013 }
1014
1015 /* convert to 1/d_{ij} */
1016 invert_sqrt_vec(len, dist_accumulator);
1017 /* detect overflows */
1018 for (j = 0; j < len; j++) {
1019 if (dist_accumulator[j] >= FLT_MAX || dist_accumulator[j] < 0) {
1020 dist_accumulator[j] = 0;
1021 }
1022 }
1023
1024 count++; /* save place for the main diagonal entry */
1025 degree = 0;
1026 if (exp == 2) {
1027 for (j = 0; j < len; j++, count++) {
1028 val = lap1[count] *= dist_accumulator[j];
1029 degree += val;
1030 degrees[i + j + 1] -= val;
1031 }
1032 } else {
1033 for (j = 0; j < len; j++, count++) {
1034 val = lap1[count] = dist_accumulator[j];
1035 degree += val;
1036 degrees[i + j + 1] -= val;
1037 }
1038 }
1039 degrees[i] -= degree;
1040 }
1041 for (step = n, count = 0, i = 0; i < n; i++, count += step, step--) {
1042 lap1[count] = degrees[i];
1043 }
1044
1045 /* Now compute b[] */
1046 for (k = 0; k < dim; k++) {
1047 /* b[k] := lap1*coords[k] */
1048 right_mult_with_vector_ff(lap1, n, coords[k], b[k]);
1049 }
1050
1051
1052 /* compute new stress */
1053 /* remember that the Laplacians are negated, so we subtract instead of add and vice versa */
1054 new_stress = 0;
1055 for (k = 0; k < dim; k++) {
1056 new_stress += vectors_inner_productf(n, coords[k], b[k]);
1057 }
1058 new_stress *= 2;
1059 new_stress += constant_term; /* only after mult by 2 */
1060 for (k = 0; k < dim; k++) {
1061 right_mult_with_vector_ff(lap2, n, coords[k], tmp_coords);
1062 new_stress -= vectors_inner_productf(n, coords[k], tmp_coords);
1063 }
1064 /* Invariant: old_stress > 0. In theory, old_stress >= new_stress
1065 * but we use fabs in case of numerical error.
1066 */
1067 {
1068 double diff = old_stress - new_stress;
1069 double change = fabs(diff);
1070 converged = change / old_stress < Epsilon || new_stress < Epsilon;
1071 }
1072 old_stress = new_stress;
1073
1074 for (k = 0; k < dim; k++) {
1075 node_t *np;
1076 if (havePinned) {
1077 copy_vectorf(n, coords[k], tmp_coords);
1078 if (conjugate_gradient_mkernel(lap2, tmp_coords, b[k], n,
1079 conj_tol, n) < 0) {
1080 iterations = -1;
1081 goto finish1;
1082 }
1083 for (i = 0; i < n; i++) {
1084 np = nodes[i];
1085 if (!isFixed(np))
1086 coords[k][i] = tmp_coords[i];
1087 }
1088 } else {
1089 if (conjugate_gradient_mkernel(lap2, coords[k], b[k], n,
1090 conj_tol, n) < 0) {
1091 iterations = -1;
1092 goto finish1;
1093 }
1094 }
1095 }
1096 if (Verbose && iterations % 5 == 0) {
1097 fprintf(stderr, "%.3f ", new_stress);
1098 if ((iterations + 5) % 50 == 0)
1099 fprintf(stderr, "\n");
1100 }
1101 }
1102 if (Verbose) {
1103 fprintf(stderr, "\nfinal e = %f %d iterations %.2f sec\n",
1104 compute_stressf(coords, lap2, dim, n, exp),
1105 iterations, elapsed_sec());
1106 }
1107
1108 for (i = 0; i < dim; i++) {
1109 for (j = 0; j < n; j++) {
1110 d_coords[i][j] = coords[i][j];
1111 }
1112 }
1113finish1:
1114 free(f_storage);
1115 free(coords);
1116
1117 free(lap2);
1118 if (b) {
1119 free(b[0]);
1120 free(b);
1121 }
1122 free(tmp_coords);
1123 free(dist_accumulator);
1124 free(degrees);
1125 free(lap1);
1126 return iterations;
1127}
Memory allocation wrappers that exit on failure.
static void * gv_calloc(size_t nmemb, size_t size)
Definition alloc.h:26
#define Epsilon
Definition arcball.h:140
#define MIN(a, b)
Definition arith.h:28
void bfs(int vertex, vtx_data *graph, int n, DistType *dist)
compute vector dist of distances of all nodes from vertex
Definition bfs.c:22
int solveCircuit(int nG, double **Gm, double **Gm_inv)
Definition circuit.c:19
double drand48(void)
Definition utils.c:1547
int conjugate_gradient_mkernel(float *A, float *x, float *b, int n, double tol, int max_iterations)
Definition conjgrad.c:160
int conjugate_gradient_f(float **A, double *x, double *b, int n, double tol, int max_iterations, bool ortho1)
Definition conjgrad.c:91
void embed_graph(vtx_data *graph, int n, int dim, DistType ***Coords, int reweight_graph)
Definition embed_graph.c:28
void center_coordinate(DistType **coords, int n, int dim)
static double dist(int dim, double *x, double *y)
static double len(glCompPoint p)
Definition glutils.c:136
static bool Verbose
Definition gml2gv.c:24
void free(void *)
node NULL
Definition grammar.y:181
void agwarningf(const char *fmt,...)
Definition agerror.c:173
int agerr(agerrlevel_t level, const char *fmt,...)
Definition agerror.c:155
@ AGPREV
Definition cgraph.h:946
#define ND_pos(n)
Definition types.h:520
Agraph_t * graph(char *name)
Definition gv.cpp:32
static opts_t opts
Definition gvgen.c:399
void compute_new_weights(vtx_data *graph, int n)
Definition kkutils.c:163
double distance_kD(double **coords, int dim, int i, int j)
Definition kkutils.c:111
void fill_neighbors_vec_unweighted(vtx_data *graph, int vtx, int *vtx_vec)
Definition kkutils.c:32
size_t common_neighbors(vtx_data *graph, int u, int *v_vector)
Definition kkutils.c:19
void restore_old_weights(vtx_data *graph, int n, float *old_weights)
Definition kkutils.c:194
void empty_neighbors_vec(vtx_data *graph, int vtx, int *vtx_vec)
Definition kkutils.c:41
void dijkstra_f(int vertex, vtx_data *graph, int n, float *dist)
Definition dijkstra.c:256
void ngdijkstra(int vertex, vtx_data *graph, int n, DistType *dist)
Definition dijkstra.c:139
#define isFixed(n)
Definition macros.h:19
#define hasPos(n)
Definition macros.h:18
#define neighbor(t, i, edim, elist)
Definition make_map.h:41
void right_mult_with_vector_transpose(double **matrix, int dim1, int dim2, double *vector, double *result)
Definition matrix_ops.c:348
void invert_vec(int n, float *vec)
Definition matrix_ops.c:507
void right_mult_with_vector_d(double **matrix, int dim1, int dim2, double *vector, double *result)
Definition matrix_ops.c:366
void mult_sparse_dense_mat_transpose(vtx_data *A, double **B, int dim1, int dim2, float ***CC)
Definition matrix_ops.c:186
void invert_sqrt_vec(int n, float *vec)
Definition matrix_ops.c:527
void mult_dense_mat(double **A, float **B, int dim1, int dim2, int dim3, float ***CC)
Definition matrix_ops.c:133
void set_vector_valf(int n, float val, float *result)
Definition matrix_ops.c:482
void orthog1(int n, double *vec)
Definition matrix_ops.c:231
void copy_vectorf(int n, float *source, float *dest)
Definition matrix_ops.c:457
void sqrt_vecf(int n, float *source, float *target)
Definition matrix_ops.c:517
void right_mult_with_vector_ff(float *packed_matrix, int n, float *vector, float *result)
Definition matrix_ops.c:407
void square_vec(int n, float *vec)
Definition matrix_ops.c:499
double vectors_inner_productf(int n, float *vector1, float *vector2)
Definition matrix_ops.c:464
#define delta
Definition maze.c:136
static const int dim
#define MODEL_SUBSET
Definition neato.h:18
#define MODEL_MDS
Definition neato.h:19
#define MODEL_CIRCUIT
Definition neato.h:17
NEATOPROCS_API void free_array(double **rv)
Definition stuff.c:57
NEATOPROCS_API double ** new_array(int i, int j, double val)
Definition stuff.c:42
void PCA_alloc(DistType **coords, int dim, int n, double **new_coords, int new_dim)
Definition pca.c:23
bool iterativePCA_1D(double **coords, int dim, int n, double *new_direction)
Definition pca.c:72
static int nedges
total no. of edges used in routing
Definition routespl.c:32
int DistType
Definition sparsegraph.h:38
float * compute_apsp_artificial_weights_packed(vtx_data *graph, int n)
Definition stress.c:712
static double compute_stressf(float **coords, float *lap, int dim, int n, int exp)
Definition stress.c:42
int stress_majorization_kD_mkernel(vtx_data *graph, int n, double **d_coords, node_t **nodes, int dim, int opts, int model, int maxi)
at present, if any nodes have pos set, smart_ini is false
Definition stress.c:793
#define DegType
Definition stress.c:790
static float * compute_weighted_apsp_packed(vtx_data *graph, int n)
Definition stress.c:643
static int sparse_stress_subspace_majorization_kD(vtx_data *graph, int n, double **coords, int dim, int smart_ini, int exp, int reweight_graph, int n_iterations, int num_centers)
Definition stress.c:227
float * circuitModel(vtx_data *graph, int nG)
Definition stress.c:167
int initLayout(int n, int dim, double **coords, node_t **nodes)
Definition stress.c:129
float * mdsModel(vtx_data *graph, int nG)
update matrix with actual edge lengths
Definition stress.c:663
float * compute_apsp_packed(vtx_data *graph, int n)
assumes integral weights > 0
Definition stress.c:694
static double compute_stress1(double **coords, dist_data *distances, int dim, int n, int exp)
Definition stress.c:75
#define stress_pca_dim
Definition stress.c:32
#define opt_smart_init
Definition stress.h:30
#define opt_exp_flag
Definition stress.h:31
#define tolerance_cg
Definition stress.h:21
#define num_pivots_stress
Definition stress.h:28
DistType * edist
Definition stress.c:38
bool free_mem
Definition stress.c:39
size_t nedges
Definition stress.c:36
int * edges
Definition stress.c:37
float * ewgts
Definition sparsegraph.h:31
unsigned nedges
unsigned * edges
double elapsed_sec(void)
Definition timing.c:21
void start_timer(void)
Definition timing.c:19
#define MAX(a, b)
Definition write.c:32