Graphviz 14.1.3~dev.20260126.0926
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 "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#if defined(DEBUG) && DEBUG > 1
777static void dumpMatrix(float *Dij, int n)
778{
779 int i, j, count = 0;
780 for (i = 0; i < n; i++) {
781 for (j = i; j < n; j++) {
782 fprintf(stderr, "%.02f ", Dij[count++]);
783 }
784 fputs("\n", stderr);
785 }
786}
787#endif
788
789/* Accumulator type for diagonal of Laplacian. Needs to be as large
790 * as possible. Use long double; configure to double if necessary.
791 */
792#define DegType long double
793
795int stress_majorization_kD_mkernel(vtx_data * graph, /* Input graph in sparse representation */
796 int n, /* Number of nodes */
797 double **d_coords, /* coordinates of nodes (output layout) */
798 node_t ** nodes, /* original nodes */
799 int dim, /* dimensionality of layout */
800 int opts, /* options */
801 int model, /* model */
802 int maxi /* max iterations */
803 )
804{
805 int iterations; /* output: number of iteration of the process */
806
807 double conj_tol = tolerance_cg; /* tolerance of Conjugate Gradient */
808 float *Dij = NULL;
809 int i, j, k;
810 float **coords = NULL;
811 float *f_storage = NULL;
812 float constant_term;
813 int count;
814 DegType degree;
815 int lap_length;
816 float *lap2 = NULL;
817 DegType *degrees = NULL;
818 int step;
819 float val;
820 double old_stress, new_stress;
821 bool converged;
822 float **b = NULL;
823 float *tmp_coords = NULL;
824 float *dist_accumulator = NULL;
825 float *lap1 = NULL;
826 int smart_ini = opts & opt_smart_init;
827 int exp = opts & opt_exp_flag;
828 int len;
829 int havePinned; /* some node is pinned */
830
831 /*************************************************
832 ** Computation of full, dense, unrestricted k-D **
833 ** stress minimization by majorization **
834 *************************************************/
835
836 /****************************************************
837 ** Compute the all-pairs-shortest-distances matrix **
838 ****************************************************/
839
840 if (maxi < 0)
841 return 0;
842
843 if (Verbose)
844 start_timer();
845
846 if (model == MODEL_SUBSET) {
847 /* weight graph to separate high-degree nodes */
848 /* and perform slower Dijkstra-based computation */
849 if (Verbose)
850 fprintf(stderr, "Calculating subset model");
852 } else if (model == MODEL_CIRCUIT) {
853 Dij = circuitModel(graph, n);
854 if (!Dij) {
856 "graph is disconnected. Hence, the circuit model\n");
858 "is undefined. Reverting to the shortest path model.\n");
859 }
860 } else if (model == MODEL_MDS) {
861 if (Verbose)
862 fprintf(stderr, "Calculating MDS model");
863 Dij = mdsModel(graph, n);
864 }
865 if (!Dij) {
866 if (Verbose)
867 fprintf(stderr, "Calculating shortest paths");
868 if (graph->ewgts)
870 else
871 Dij = compute_apsp_packed(graph, n);
872 }
873
874 if (Verbose) {
875 fprintf(stderr, ": %.2f sec\n", elapsed_sec());
876 fprintf(stderr, "Setting initial positions");
877 start_timer();
878 }
879
880 /**************************
881 ** Layout initialization **
882 **************************/
883
884 if (smart_ini && n > 1) {
885 havePinned = 0;
886 /* optimize layout quickly within subspace */
887 /* perform at most 50 iterations within 30-D subspace to
888 get an estimate */
890 d_coords, dim, smart_ini, exp,
891 model == MODEL_SUBSET, 50,
892 num_pivots_stress) < 0) {
893 iterations = -1;
894 goto finish1;
895 }
896
897 for (i = 0; i < dim; i++) {
898 /* for numerical stability, scale down layout */
899 double max = 1;
900 for (j = 0; j < n; j++) {
901 if (fabs(d_coords[i][j]) > max) {
902 max = fabs(d_coords[i][j]);
903 }
904 }
905 for (j = 0; j < n; j++) {
906 d_coords[i][j] /= max;
907 }
908 /* add small random noise */
909 for (j = 0; j < n; j++) {
910 d_coords[i][j] += 1e-6 * (drand48() - 0.5);
911 }
912 orthog1(n, d_coords[i]);
913 }
914 } else {
915 havePinned = initLayout(n, dim, d_coords, nodes);
916 }
917 if (Verbose)
918 fprintf(stderr, ": %.2f sec", elapsed_sec());
919 if (n == 1 || maxi == 0) {
920 free(Dij);
921 return 0;
922 }
923
924 if (Verbose) {
925 fprintf(stderr, ": %.2f sec\n", elapsed_sec());
926 fprintf(stderr, "Setting up stress function");
927 start_timer();
928 }
929 coords = gv_calloc(dim, sizeof(float *));
930 f_storage = gv_calloc(dim * n, sizeof(float));
931 for (i = 0; i < dim; i++) {
932 coords[i] = f_storage + i * n;
933 for (j = 0; j < n; j++) {
934 coords[i][j] = (float)d_coords[i][j];
935 }
936 }
937
938 /* compute constant term in stress sum */
939 /* which is \sum_{i<j} w_{ij}d_{ij}^2 */
940 assert(exp == 1 || exp == 2);
941 constant_term = (float)n * (n - 1) / 2;
942
943 /**************************
944 ** Laplacian computation **
945 **************************/
946
947 lap_length = n * (n + 1) / 2;
948 lap2 = Dij;
949 if (exp == 2) {
950 square_vec(lap_length, lap2);
951 }
952 /* compute off-diagonal entries */
953 invert_vec(lap_length, lap2);
954
955 /* compute diagonal entries */
956 count = 0;
957 degrees = gv_calloc(n, sizeof(DegType));
958 for (i = 0; i < n - 1; i++) {
959 degree = 0;
960 count++; /* skip main diag entry */
961 for (j = 1; j < n - i; j++, count++) {
962 val = lap2[count];
963 degree += val;
964 degrees[i + j] -= val;
965 }
966 degrees[i] -= degree;
967 }
968 for (step = n, count = 0, i = 0; i < n; i++, count += step, step--) {
969 lap2[count] = degrees[i];
970 }
971
972 /*************************
973 ** Layout optimization **
974 *************************/
975
976 b = gv_calloc(dim, sizeof(float *));
977 b[0] = gv_calloc(dim * n, sizeof(float));
978 for (k = 1; k < dim; k++) {
979 b[k] = b[0] + k * n;
980 }
981
982 tmp_coords = gv_calloc(n, sizeof(float));
983 dist_accumulator = gv_calloc(n, sizeof(float));
984 lap1 = gv_calloc(lap_length, sizeof(float));
985
986
987 old_stress = DBL_MAX; // at least one iteration
988 if (Verbose) {
989 fprintf(stderr, ": %.2f sec\n", elapsed_sec());
990 fprintf(stderr, "Solving model: ");
991 start_timer();
992 }
993
994 for (converged = false, iterations = 0;
995 iterations < maxi && !converged; iterations++) {
996
997 /* First, construct Laplacian of 1/(d_ij*|p_i-p_j|) */
998 /* set_vector_val(n, 0, degrees); */
999 memset(degrees, 0, n * sizeof(DegType));
1000 if (exp == 2) {
1001 sqrt_vecf(lap_length, lap2, lap1);
1002 }
1003 for (count = 0, i = 0; i < n - 1; i++) {
1004 len = n - i - 1;
1005 /* init 'dist_accumulator' with zeros */
1006 set_vector_valf(len, 0, dist_accumulator);
1007
1008 /* put into 'dist_accumulator' all squared distances between 'i' and 'i'+1,...,'n'-1 */
1009 for (k = 0; k < dim; k++) {
1010 size_t x;
1011 for (x = 0; x < (size_t)len; ++x) {
1012 float tmp = coords[k][i] + -1.0f * (coords[k] + i + 1)[x];
1013 dist_accumulator[x] += tmp * tmp;
1014 }
1015 }
1016
1017 /* convert to 1/d_{ij} */
1018 invert_sqrt_vec(len, dist_accumulator);
1019 /* detect overflows */
1020 for (j = 0; j < len; j++) {
1021 if (dist_accumulator[j] >= FLT_MAX || dist_accumulator[j] < 0) {
1022 dist_accumulator[j] = 0;
1023 }
1024 }
1025
1026 count++; /* save place for the main diagonal entry */
1027 degree = 0;
1028 if (exp == 2) {
1029 for (j = 0; j < len; j++, count++) {
1030 val = lap1[count] *= dist_accumulator[j];
1031 degree += val;
1032 degrees[i + j + 1] -= val;
1033 }
1034 } else {
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 }
1041 degrees[i] -= degree;
1042 }
1043 for (step = n, count = 0, i = 0; i < n; i++, count += step, step--) {
1044 lap1[count] = degrees[i];
1045 }
1046
1047 /* Now compute b[] */
1048 for (k = 0; k < dim; k++) {
1049 /* b[k] := lap1*coords[k] */
1050 right_mult_with_vector_ff(lap1, n, coords[k], b[k]);
1051 }
1052
1053
1054 /* compute new stress */
1055 /* remember that the Laplacians are negated, so we subtract instead of add and vice versa */
1056 new_stress = 0;
1057 for (k = 0; k < dim; k++) {
1058 new_stress += vectors_inner_productf(n, coords[k], b[k]);
1059 }
1060 new_stress *= 2;
1061 new_stress += constant_term; /* only after mult by 2 */
1062 for (k = 0; k < dim; k++) {
1063 right_mult_with_vector_ff(lap2, n, coords[k], tmp_coords);
1064 new_stress -= vectors_inner_productf(n, coords[k], tmp_coords);
1065 }
1066 /* Invariant: old_stress > 0. In theory, old_stress >= new_stress
1067 * but we use fabs in case of numerical error.
1068 */
1069 {
1070 double diff = old_stress - new_stress;
1071 double change = fabs(diff);
1072 converged = change / old_stress < Epsilon || new_stress < Epsilon;
1073 }
1074 old_stress = new_stress;
1075
1076 for (k = 0; k < dim; k++) {
1077 node_t *np;
1078 if (havePinned) {
1079 copy_vectorf(n, coords[k], tmp_coords);
1080 if (conjugate_gradient_mkernel(lap2, tmp_coords, b[k], n,
1081 conj_tol, n) < 0) {
1082 iterations = -1;
1083 goto finish1;
1084 }
1085 for (i = 0; i < n; i++) {
1086 np = nodes[i];
1087 if (!isFixed(np))
1088 coords[k][i] = tmp_coords[i];
1089 }
1090 } else {
1091 if (conjugate_gradient_mkernel(lap2, coords[k], b[k], n,
1092 conj_tol, n) < 0) {
1093 iterations = -1;
1094 goto finish1;
1095 }
1096 }
1097 }
1098 if (Verbose && iterations % 5 == 0) {
1099 fprintf(stderr, "%.3f ", new_stress);
1100 if ((iterations + 5) % 50 == 0)
1101 fprintf(stderr, "\n");
1102 }
1103 }
1104 if (Verbose) {
1105 fprintf(stderr, "\nfinal e = %f %d iterations %.2f sec\n",
1106 compute_stressf(coords, lap2, dim, n, exp),
1107 iterations, elapsed_sec());
1108 }
1109
1110 for (i = 0; i < dim; i++) {
1111 for (j = 0; j < n; j++) {
1112 d_coords[i][j] = coords[i][j];
1113 }
1114 }
1115finish1:
1116 free(f_storage);
1117 free(coords);
1118
1119 free(lap2);
1120 if (b) {
1121 free(b[0]);
1122 free(b);
1123 }
1124 free(tmp_coords);
1125 free(dist_accumulator);
1126 free(degrees);
1127 free(lap1);
1128 return iterations;
1129}
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
#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:1549
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:795
#define DegType
Definition stress.c:792
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