Graphviz 13.0.0~dev.20241220.2304
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/* initLayout:
126 * Initialize node coordinates. If the node already has
127 * a position, use it.
128 * Return true if some node is fixed.
129 */
130int initLayout(int n, int dim, double **coords, node_t **nodes) {
131 node_t *np;
132 double *xp;
133 double *yp;
134 double *pt;
135 int i, d;
136 int pinned = 0;
137
138 xp = coords[0];
139 yp = coords[1];
140 for (i = 0; i < n; i++) {
141 np = nodes[i];
142 if (hasPos(np)) {
143 pt = ND_pos(np);
144 *xp++ = *pt++;
145 *yp++ = *pt++;
146 if (dim > 2) {
147 for (d = 2; d < dim; d++)
148 coords[d][i] = *pt++;
149 }
150 if (isFixed(np))
151 pinned = 1;
152 } else {
153 *xp++ = drand48();
154 *yp++ = drand48();
155 if (dim > 2) {
156 for (d = 2; d < dim; d++)
157 coords[d][i] = drand48();
158 }
159 }
160 }
161
162 for (d = 0; d < dim; d++)
163 orthog1(n, coords[d]);
164
165 return pinned;
166}
167
168float *circuitModel(vtx_data * graph, int nG)
169{
170 int i, j, rv, count;
171 float *Dij = gv_calloc(nG * (nG + 1) / 2, sizeof(float));
172 double **Gm;
173 double **Gm_inv;
174
175 Gm = new_array(nG, nG, 0.0);
176 Gm_inv = new_array(nG, nG, 0.0);
177
178 /* set non-diagonal entries */
179 if (graph->ewgts) {
180 for (i = 0; i < nG; i++) {
181 for (size_t e = 1; e < graph[i].nedges; e++) {
182 j = graph[i].edges[e];
183 /* conductance is 1/resistance */
184 Gm[i][j] = Gm[j][i] = -1.0 / graph[i].ewgts[e]; /* negate */
185 }
186 }
187 } else {
188 for (i = 0; i < nG; i++) {
189 for (size_t e = 1; e < graph[i].nedges; e++) {
190 j = graph[i].edges[e];
191 /* conductance is 1/resistance */
192 Gm[i][j] = Gm[j][i] = -1.0; /* ewgts are all 1 */
193 }
194 }
195 }
196
197 rv = solveCircuit(nG, Gm, Gm_inv);
198
199 if (rv) {
200 float v;
201 count = 0;
202 for (i = 0; i < nG; i++) {
203 for (j = i; j < nG; j++) {
204 if (i == j)
205 v = 0.0;
206 else
207 v = (float) (Gm_inv[i][i] + Gm_inv[j][j] -
208 2.0 * Gm_inv[i][j]);
209 Dij[count++] = v;
210 }
211 }
212 } else {
213 free(Dij);
214 Dij = NULL;
215 }
216 free_array(Gm);
217 free_array(Gm_inv);
218 return Dij;
219}
220
221/* sparse_stress_subspace_majorization_kD:
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, /* dimemsionality 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 uniformaly 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 dijkstra(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 dijkstra(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
664/* mdsModel:
665 * Update matrix with actual edge lengths
666 */
667float *mdsModel(vtx_data * graph, int nG)
668{
669 int i, j;
670 float *Dij;
671 int shift = 0;
672 double delta = 0.0;
673
674 if (graph->ewgts == NULL)
675 return 0;
676
677 /* first, compute shortest paths to fill in non-edges */
679
680 /* then, replace edge entries will user-supplied len */
681 for (i = 0; i < nG; i++) {
682 shift += i;
683 for (size_t e = 1; e < graph[i].nedges; e++) {
684 j = graph[i].edges[e];
685 if (j < i)
686 continue;
687 delta += fabsf(Dij[i * nG + j - shift] - graph[i].ewgts[e]);
688 Dij[i * nG + j - shift] = graph[i].ewgts[e];
689 }
690 }
691 if (Verbose) {
692 fprintf(stderr, "mdsModel: delta = %f\n", delta);
693 }
694 return Dij;
695}
696
697/* compute_apsp_packed:
698 * Assumes integral weights > 0.
699 */
701{
702 int i, j, count;
703 float *Dij = gv_calloc(n * (n + 1) / 2, sizeof(float));
704
705 DistType *Di = gv_calloc(n, sizeof(DistType));
706
707 count = 0;
708 for (i = 0; i < n; i++) {
709 bfs(i, graph, n, Di);
710 for (j = i; j < n; j++) {
711 Dij[count++] = (float)Di[j];
712 }
713 }
714 free(Di);
715 return Dij;
716}
717
719 /* compute all-pairs-shortest-path-length while weighting the graph */
720 /* so high-degree nodes are distantly located */
721
722 float *Dij;
723 int i;
724 float *old_weights = graph[0].ewgts;
725 size_t nedges = 0;
726 size_t deg_i, deg_j;
727 int neighbor;
728
729 for (i = 0; i < n; i++) {
730 nedges += graph[i].nedges;
731 }
732
733 float *weights = gv_calloc(nedges, sizeof(float));
734 int *vtx_vec = gv_calloc(n, sizeof(int));
735
736 if (graph->ewgts) {
737 for (i = 0; i < n; i++) {
739 deg_i = graph[i].nedges - 1;
740 for (size_t j = 1; j <= deg_i; j++) {
741 neighbor = graph[i].edges[j];
742 deg_j = graph[neighbor].nedges - 1;
743 weights[j] = fmaxf((float)(deg_i + deg_j -
744 2 * common_neighbors(graph, neighbor, vtx_vec)), graph[i].ewgts[j]);
745 }
746 empty_neighbors_vec(graph, i, vtx_vec);
747 graph[i].ewgts = weights;
748 weights += graph[i].nedges;
749 }
751 } else {
752 for (i = 0; i < n; i++) {
753 graph[i].ewgts = weights;
755 deg_i = graph[i].nedges - 1;
756 for (size_t j = 1; j <= deg_i; j++) {
757 neighbor = graph[i].edges[j];
758 deg_j = graph[neighbor].nedges - 1;
759 weights[j] =
760 (float)(deg_i + deg_j - 2 * common_neighbors(graph, neighbor, vtx_vec));
761 }
762 empty_neighbors_vec(graph, i, vtx_vec);
763 weights += graph[i].nedges;
764 }
765 Dij = compute_apsp_packed(graph, n);
766 }
767
768 free(vtx_vec);
769 free(graph[0].ewgts);
770 graph[0].ewgts = NULL;
771 if (old_weights != NULL) {
772 for (i = 0; i < n; i++) {
773 graph[i].ewgts = old_weights;
774 old_weights += graph[i].nedges;
775 }
776 }
777 return Dij;
778}
779
780#if defined(DEBUG) && DEBUG > 1
781static void dumpMatrix(float *Dij, int n)
782{
783 int i, j, count = 0;
784 for (i = 0; i < n; i++) {
785 for (j = i; j < n; j++) {
786 fprintf(stderr, "%.02f ", Dij[count++]);
787 }
788 fputs("\n", stderr);
789 }
790}
791#endif
792
793/* Accumulator type for diagonal of Laplacian. Needs to be as large
794 * as possible. Use long double; configure to double if necessary.
795 */
796#define DegType long double
797
798/* stress_majorization_kD_mkernel:
799 * At present, if any nodes have pos set, smart_ini is false.
800 */
801int stress_majorization_kD_mkernel(vtx_data * graph, /* Input graph in sparse representation */
802 int n, /* Number of nodes */
803 double **d_coords, /* coordinates of nodes (output layout) */
804 node_t ** nodes, /* original nodes */
805 int dim, /* dimemsionality of layout */
806 int opts, /* options */
807 int model, /* model */
808 int maxi /* max iterations */
809 )
810{
811 int iterations; /* output: number of iteration of the process */
812
813 double conj_tol = tolerance_cg; /* tolerance of Conjugate Gradient */
814 float *Dij = NULL;
815 int i, j, k;
816 float **coords = NULL;
817 float *f_storage = NULL;
818 float constant_term;
819 int count;
820 DegType degree;
821 int lap_length;
822 float *lap2 = NULL;
823 DegType *degrees = NULL;
824 int step;
825 float val;
826 double old_stress, new_stress;
827 bool converged;
828 float **b = NULL;
829 float *tmp_coords = NULL;
830 float *dist_accumulator = NULL;
831 float *lap1 = NULL;
832 int smart_ini = opts & opt_smart_init;
833 int exp = opts & opt_exp_flag;
834 int len;
835 int havePinned; /* some node is pinned */
836
837 /*************************************************
838 ** Computation of full, dense, unrestricted k-D **
839 ** stress minimization by majorization **
840 *************************************************/
841
842 /****************************************************
843 ** Compute the all-pairs-shortest-distances matrix **
844 ****************************************************/
845
846 if (maxi < 0)
847 return 0;
848
849 if (Verbose)
850 start_timer();
851
852 if (model == MODEL_SUBSET) {
853 /* weight graph to separate high-degree nodes */
854 /* and perform slower Dijkstra-based computation */
855 if (Verbose)
856 fprintf(stderr, "Calculating subset model");
858 } else if (model == MODEL_CIRCUIT) {
859 Dij = circuitModel(graph, n);
860 if (!Dij) {
862 "graph is disconnected. Hence, the circuit model\n");
864 "is undefined. Reverting to the shortest path model.\n");
865 }
866 } else if (model == MODEL_MDS) {
867 if (Verbose)
868 fprintf(stderr, "Calculating MDS model");
869 Dij = mdsModel(graph, n);
870 }
871 if (!Dij) {
872 if (Verbose)
873 fprintf(stderr, "Calculating shortest paths");
874 if (graph->ewgts)
876 else
877 Dij = compute_apsp_packed(graph, n);
878 }
879
880 if (Verbose) {
881 fprintf(stderr, ": %.2f sec\n", elapsed_sec());
882 fprintf(stderr, "Setting initial positions");
883 start_timer();
884 }
885
886 /**************************
887 ** Layout initialization **
888 **************************/
889
890 if (smart_ini && n > 1) {
891 havePinned = 0;
892 /* optimize layout quickly within subspace */
893 /* perform at most 50 iterations within 30-D subspace to
894 get an estimate */
896 d_coords, dim, smart_ini, exp,
897 model == MODEL_SUBSET, 50,
898 num_pivots_stress) < 0) {
899 iterations = -1;
900 goto finish1;
901 }
902
903 for (i = 0; i < dim; i++) {
904 /* for numerical stability, scale down layout */
905 double max = 1;
906 for (j = 0; j < n; j++) {
907 if (fabs(d_coords[i][j]) > max) {
908 max = fabs(d_coords[i][j]);
909 }
910 }
911 for (j = 0; j < n; j++) {
912 d_coords[i][j] /= max;
913 }
914 /* add small random noise */
915 for (j = 0; j < n; j++) {
916 d_coords[i][j] += 1e-6 * (drand48() - 0.5);
917 }
918 orthog1(n, d_coords[i]);
919 }
920 } else {
921 havePinned = initLayout(n, dim, d_coords, nodes);
922 }
923 if (Verbose)
924 fprintf(stderr, ": %.2f sec", elapsed_sec());
925 if (n == 1 || maxi == 0) {
926 free(Dij);
927 return 0;
928 }
929
930 if (Verbose) {
931 fprintf(stderr, ": %.2f sec\n", elapsed_sec());
932 fprintf(stderr, "Setting up stress function");
933 start_timer();
934 }
935 coords = gv_calloc(dim, sizeof(float *));
936 f_storage = gv_calloc(dim * n, sizeof(float));
937 for (i = 0; i < dim; i++) {
938 coords[i] = f_storage + i * n;
939 for (j = 0; j < n; j++) {
940 coords[i][j] = (float)d_coords[i][j];
941 }
942 }
943
944 /* compute constant term in stress sum */
945 /* which is \sum_{i<j} w_{ij}d_{ij}^2 */
946 assert(exp == 1 || exp == 2);
947 constant_term = (float)n * (n - 1) / 2;
948
949 /**************************
950 ** Laplacian computation **
951 **************************/
952
953 lap_length = n * (n + 1) / 2;
954 lap2 = Dij;
955 if (exp == 2) {
956 square_vec(lap_length, lap2);
957 }
958 /* compute off-diagonal entries */
959 invert_vec(lap_length, lap2);
960
961 /* compute diagonal entries */
962 count = 0;
963 degrees = gv_calloc(n, sizeof(DegType));
964 for (i = 0; i < n - 1; i++) {
965 degree = 0;
966 count++; /* skip main diag entry */
967 for (j = 1; j < n - i; j++, count++) {
968 val = lap2[count];
969 degree += val;
970 degrees[i + j] -= val;
971 }
972 degrees[i] -= degree;
973 }
974 for (step = n, count = 0, i = 0; i < n; i++, count += step, step--) {
975 lap2[count] = degrees[i];
976 }
977
978 /*************************
979 ** Layout optimization **
980 *************************/
981
982 b = gv_calloc(dim, sizeof(float *));
983 b[0] = gv_calloc(dim * n, sizeof(float));
984 for (k = 1; k < dim; k++) {
985 b[k] = b[0] + k * n;
986 }
987
988 tmp_coords = gv_calloc(n, sizeof(float));
989 dist_accumulator = gv_calloc(n, sizeof(float));
990 lap1 = gv_calloc(lap_length, sizeof(float));
991
992
993 old_stress = DBL_MAX; // at least one iteration
994 if (Verbose) {
995 fprintf(stderr, ": %.2f sec\n", elapsed_sec());
996 fprintf(stderr, "Solving model: ");
997 start_timer();
998 }
999
1000 for (converged = false, iterations = 0;
1001 iterations < maxi && !converged; iterations++) {
1002
1003 /* First, construct Laplacian of 1/(d_ij*|p_i-p_j|) */
1004 /* set_vector_val(n, 0, degrees); */
1005 memset(degrees, 0, n * sizeof(DegType));
1006 if (exp == 2) {
1007 sqrt_vecf(lap_length, lap2, lap1);
1008 }
1009 for (count = 0, i = 0; i < n - 1; i++) {
1010 len = n - i - 1;
1011 /* init 'dist_accumulator' with zeros */
1012 set_vector_valf(len, 0, dist_accumulator);
1013
1014 /* put into 'dist_accumulator' all squared distances between 'i' and 'i'+1,...,'n'-1 */
1015 for (k = 0; k < dim; k++) {
1016 size_t x;
1017 for (x = 0; x < (size_t)len; ++x) {
1018 float tmp = coords[k][i] + -1.0f * (coords[k] + i + 1)[x];
1019 dist_accumulator[x] += tmp * tmp;
1020 }
1021 }
1022
1023 /* convert to 1/d_{ij} */
1024 invert_sqrt_vec(len, dist_accumulator);
1025 /* detect overflows */
1026 for (j = 0; j < len; j++) {
1027 if (dist_accumulator[j] >= FLT_MAX || dist_accumulator[j] < 0) {
1028 dist_accumulator[j] = 0;
1029 }
1030 }
1031
1032 count++; /* save place for the main diagonal entry */
1033 degree = 0;
1034 if (exp == 2) {
1035 for (j = 0; j < len; j++, count++) {
1036 val = lap1[count] *= dist_accumulator[j];
1037 degree += val;
1038 degrees[i + j + 1] -= val;
1039 }
1040 } else {
1041 for (j = 0; j < len; j++, count++) {
1042 val = lap1[count] = dist_accumulator[j];
1043 degree += val;
1044 degrees[i + j + 1] -= val;
1045 }
1046 }
1047 degrees[i] -= degree;
1048 }
1049 for (step = n, count = 0, i = 0; i < n; i++, count += step, step--) {
1050 lap1[count] = degrees[i];
1051 }
1052
1053 /* Now compute b[] */
1054 for (k = 0; k < dim; k++) {
1055 /* b[k] := lap1*coords[k] */
1056 right_mult_with_vector_ff(lap1, n, coords[k], b[k]);
1057 }
1058
1059
1060 /* compute new stress */
1061 /* remember that the Laplacians are negated, so we subtract instead of add and vice versa */
1062 new_stress = 0;
1063 for (k = 0; k < dim; k++) {
1064 new_stress += vectors_inner_productf(n, coords[k], b[k]);
1065 }
1066 new_stress *= 2;
1067 new_stress += constant_term; /* only after mult by 2 */
1068 for (k = 0; k < dim; k++) {
1069 right_mult_with_vector_ff(lap2, n, coords[k], tmp_coords);
1070 new_stress -= vectors_inner_productf(n, coords[k], tmp_coords);
1071 }
1072 /* Invariant: old_stress > 0. In theory, old_stress >= new_stress
1073 * but we use fabs in case of numerical error.
1074 */
1075 {
1076 double diff = old_stress - new_stress;
1077 double change = fabs(diff);
1078 converged = change / old_stress < Epsilon || new_stress < Epsilon;
1079 }
1080 old_stress = new_stress;
1081
1082 for (k = 0; k < dim; k++) {
1083 node_t *np;
1084 if (havePinned) {
1085 copy_vectorf(n, coords[k], tmp_coords);
1086 if (conjugate_gradient_mkernel(lap2, tmp_coords, b[k], n,
1087 conj_tol, n) < 0) {
1088 iterations = -1;
1089 goto finish1;
1090 }
1091 for (i = 0; i < n; i++) {
1092 np = nodes[i];
1093 if (!isFixed(np))
1094 coords[k][i] = tmp_coords[i];
1095 }
1096 } else {
1097 if (conjugate_gradient_mkernel(lap2, coords[k], b[k], n,
1098 conj_tol, n) < 0) {
1099 iterations = -1;
1100 goto finish1;
1101 }
1102 }
1103 }
1104 if (Verbose && iterations % 5 == 0) {
1105 fprintf(stderr, "%.3f ", new_stress);
1106 if ((iterations + 5) % 50 == 0)
1107 fprintf(stderr, "\n");
1108 }
1109 }
1110 if (Verbose) {
1111 fprintf(stderr, "\nfinal e = %f %d iterations %.2f sec\n",
1112 compute_stressf(coords, lap2, dim, n, exp),
1113 iterations, elapsed_sec());
1114 }
1115
1116 for (i = 0; i < dim; i++) {
1117 for (j = 0; j < n; j++) {
1118 d_coords[i][j] = coords[i][j];
1119 }
1120 }
1121finish1:
1122 free(f_storage);
1123 free(coords);
1124
1125 free(lap2);
1126 if (b) {
1127 free(b[0]);
1128 free(b);
1129 }
1130 free(tmp_coords);
1131 free(dist_accumulator);
1132 free(degrees);
1133 free(lap1);
1134 return iterations;
1135}
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:210
#define MIN(a, b)
Definition arith.h:28
void bfs(int vertex, vtx_data *graph, int n, DistType *dist)
Definition bfs.c:25
int solveCircuit(int nG, double **Gm, double **Gm_inv)
Definition circuit.c:19
static void dijkstra(Dict_t *Q, Agraph_t *G, Agnode_t *n)
Definition dijkstra.c:188
double drand48(void)
Definition utils.c:1561
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:150
static bool Verbose
Definition gml2gv.c:23
void free(void *)
node NULL
Definition grammar.y:163
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:849
#define ND_pos(n)
Definition types.h:520
Agraph_t * graph(char *name)
Definition gv.cpp:30
static opts_t opts
Definition gvgen.c:394
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: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:196
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:258
#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:350
void invert_vec(int n, float *vec)
Definition matrix_ops.c:509
void right_mult_with_vector_d(double **matrix, int dim1, int dim2, double *vector, double *result)
Definition matrix_ops.c:368
void mult_sparse_dense_mat_transpose(vtx_data *A, double **B, int dim1, int dim2, float ***CC)
Definition matrix_ops.c:188
void invert_sqrt_vec(int n, float *vec)
Definition matrix_ops.c:529
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:484
void orthog1(int n, double *vec)
Definition matrix_ops.c:233
void copy_vectorf(int n, float *source, float *dest)
Definition matrix_ops.c:459
void sqrt_vecf(int n, float *source, float *target)
Definition matrix_ops.c:519
void right_mult_with_vector_ff(float *packed_matrix, int n, float *vector, float *result)
Definition matrix_ops.c:409
void square_vec(int n, float *vec)
Definition matrix_ops.c:501
double vectors_inner_productf(int n, float *vector1, float *vector2)
Definition matrix_ops.c:466
#define delta
Definition maze.c:133
static const int dim
#define MODEL_SUBSET
Definition neato.h:17
#define MODEL_MDS
Definition neato.h:18
#define MODEL_CIRCUIT
Definition neato.h:16
NEATOPROCS_API void free_array(double **rv)
Definition stuff.c:62
NEATOPROCS_API double ** new_array(int i, int j, double val)
Definition stuff.c:47
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:31
int DistType
Definition sparsegraph.h:37
float * compute_apsp_artificial_weights_packed(vtx_data *graph, int n)
Definition stress.c:718
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)
Definition stress.c:801
#define DegType
Definition stress.c:796
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:168
int initLayout(int n, int dim, double **coords, node_t **nodes)
Definition stress.c:130
float * mdsModel(vtx_data *graph, int nG)
Definition stress.c:667
float * compute_apsp_packed(vtx_data *graph, int n)
Definition stress.c:700
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:28
#define opt_exp_flag
Definition stress.h:29
#define tolerance_cg
Definition stress.h:19
#define num_pivots_stress
Definition stress.h:26
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:30
unsigned nedges
unsigned * edges
double elapsed_sec(void)
Definition timing.c:48
void start_timer(void)
Definition timing.c:43
#define MAX(a, b)
Definition write.c:31