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