Graphviz 16.1.1~dev.20260926.2046
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 double dist_ij;
285 double *b;
286 double *b_restricted;
287 double L_ij;
288 double old_stress, new_stress;
289 bool converged;
290
291 for (i = 0; i < subspace_dim; i++) {
292 subspace[i] = d_storage + i * n;
293 }
294
295 /* compute PHDE: */
296 num_centers_local = MIN(n, MAX(2 * subspace_dim, 50));
297 full_coords = NULL;
298 /* High dimensional embedding */
299 embed_graph(graph, n, num_centers_local, &full_coords, reweight_graph);
300 /* Centering coordinates */
301 center_coordinate(full_coords, n, num_centers_local);
302 /* PCA */
303 PCA_alloc(full_coords, num_centers_local, n, subspace, subspace_dim);
304
305 free(full_coords[0]);
306 free(full_coords);
307
308 /*************************************************
309 ** Compute the sparse-shortest-distances matrix 'distances' **
310 *************************************************/
311
312 int *CenterIndex = gv_calloc(n, sizeof(int));
313 for (i = 0; i < n; i++) {
314 CenterIndex[i] = -1;
315 }
316 invCenterIndex = NULL;
317
318 old_weights = graph[0].ewgts;
319
320 if (reweight_graph) {
321 /* weight graph to separate high-degree nodes */
322 /* in the future, perform slower Dijkstra-based computation */
324 }
325
326 /* compute sparse distance matrix */
327 /* first select 'num_centers' pivots from which we compute distance */
328 /* to all other nodes */
329
330 Dij = NULL;
331 DistType *dist = gv_calloc(n, sizeof(DistType));
332 if (num_centers == 0) { /* no pivots, skip pivots-to-nodes distance calculation */
333 goto after_pivots_selection;
334 }
335
336 invCenterIndex = gv_calloc(num_centers, sizeof(int));
337
338 storage = gv_calloc(n * num_centers, sizeof(DistType));
339 Dij = gv_calloc(num_centers, sizeof(DistType *));
340 for (i = 0; i < num_centers; i++)
341 Dij[i] = storage + i * n;
342
343 // select `num_centers` pivots that are uniformly spread over the graph
344
345 /* the first pivots is selected randomly */
346 node = rand() % n;
347 CenterIndex[node] = 0;
348 invCenterIndex[0] = node;
349
350 if (reweight_graph) {
351 ngdijkstra(node, graph, n, Dij[0]);
352 } else {
353 bfs(node, graph, n, Dij[0]);
354 }
355
356 /* find the most distant node from first pivot */
357 max_dist = 0;
358 for (i = 0; i < n; i++) {
359 dist[i] = Dij[0][i];
360 if (dist[i] > max_dist) {
361 node = i;
362 max_dist = dist[i];
363 }
364 }
365 /* select other dim-1 nodes as pivots */
366 for (i = 1; i < num_centers; i++) {
367 CenterIndex[node] = i;
368 invCenterIndex[i] = node;
369 if (reweight_graph) {
370 ngdijkstra(node, graph, n, Dij[i]);
371 } else {
372 bfs(node, graph, n, Dij[i]);
373 }
374 max_dist = 0;
375 for (int j = 0; j < n; j++) {
376 dist[j] = MIN(dist[j], Dij[i][j]);
377 if (dist[j] > max_dist
378 || (dist[j] == max_dist && rand() % (j + 1) == 0)) {
379 node = j;
380 max_dist = dist[j];
381 }
382 }
383 }
384
385 after_pivots_selection:
386
387 /* Construct a sparse distance matrix 'distances' */
388
389 /* initialize dist to -1, important for 'bfs_bounded(..)' */
390 for (i = 0; i < n; i++) {
391 dist[i] = -1;
392 }
393
394 visited_nodes = gv_calloc(n, sizeof(int));
395 distances = gv_calloc(n, sizeof(dist_data));
396 available_space = 0;
397 size_t nedges = 0;
398 for (i = 0; i < n; i++) {
399 if (CenterIndex[i] >= 0) { /* a pivot node */
400 distances[i].edges = gv_calloc(n - 1, sizeof(int));
401 distances[i].edist = gv_calloc(n - 1, sizeof(DistType));
402 distances[i].nedges = (size_t)n - 1;
403 nedges += (size_t)n - 1;
404 distances[i].free_mem = true;
405 index = CenterIndex[i];
406 for (int j = 0; j < i; j++) {
407 distances[i].edges[j] = j;
408 distances[i].edist[j] = Dij[index][j];
409 }
410 for (int j = i + 1; j < n; j++) {
411 distances[i].edges[j - 1] = j;
412 distances[i].edist[j - 1] = Dij[index][j];
413 }
414 continue;
415 }
416
417 /* a non pivot node */
418
419 num_visited_nodes = 0;
420 num_neighbors = num_visited_nodes + num_centers;
421 if (num_neighbors > available_space) {
422 available_space = n;
423 storage1 = gv_calloc(available_space, sizeof(int));
424 storage2 = gv_calloc(available_space, sizeof(DistType));
425 distances[i].free_mem = true;
426 } else {
427 distances[i].free_mem = false;
428 }
429 distances[i].edges = storage1;
430 distances[i].edist = storage2;
431 distances[i].nedges = (size_t)num_neighbors;
432 nedges += (size_t)num_neighbors;
433 for (int j = 0; j < num_visited_nodes; j++) {
434 storage1[j] = visited_nodes[j];
435 storage2[j] = dist[visited_nodes[j]];
436 dist[visited_nodes[j]] = -1;
437 }
438 /* add all pivots: */
439 for (int j = num_visited_nodes; j < num_neighbors; j++) {
440 index = j - num_visited_nodes;
441 storage1[j] = invCenterIndex[index];
442 storage2[j] = Dij[index][i];
443 }
444
445 storage1 += num_neighbors;
446 storage2 += num_neighbors;
447 available_space -= num_neighbors;
448 }
449
450 free(dist);
451 free(visited_nodes);
452
453 if (Dij != NULL) {
454 free(Dij[0]);
455 free(Dij);
456 }
457
458 /*************************************************
459 ** Laplacian computation **
460 *************************************************/
461
462 lap = gv_calloc(n, sizeof(vtx_data));
463 edges = gv_calloc(nedges + n, sizeof(int));
464 ewgts = gv_calloc(nedges + n, sizeof(float));
465 for (i = 0; i < n; i++) {
466 lap[i].edges = edges;
467 lap[i].ewgts = ewgts;
468 lap[i].nedges = distances[i].nedges + 1; /*add the self loop */
469 dist_list = distances[i].edist - 1; /* '-1' since edist[0] goes for number '1' entry in the lap */
470 degree = 0;
471 if (exp == 2) {
472 for (size_t j = 1; j < lap[i].nedges; j++) {
473 edges[j] = distances[i].edges[j - 1];
474 ewgts[j] = -1.0f / ((float)dist_list[j] * (float)dist_list[j]); // cast to float to prevent overflow
475 degree -= ewgts[j];
476 }
477 } else {
478 for (size_t j = 1; j < lap[i].nedges; j++) {
479 edges[j] = distances[i].edges[j - 1];
480 ewgts[j] = -1.0f / (float) dist_list[j];
481 degree -= ewgts[j];
482 }
483 }
484 edges[0] = i;
485 ewgts[0] = (float) degree;
486 edges += lap[i].nedges;
487 ewgts += lap[i].nedges;
488 }
489
490 /*************************************************
491 ** initialize direction vectors **
492 ** to get an initial layout **
493 *************************************************/
494
495 /* the layout is subspace*directions */
496 directions = gv_calloc(dim, sizeof(double *));
497 directions[0] = gv_calloc(dim * subspace_dim, sizeof(double));
498 for (i = 1; i < dim; i++) {
499 directions[i] = directions[0] + i * subspace_dim;
500 }
501
502 if (smart_ini) {
503 /* smart initialization */
504 for (k = 0; k < dim; k++) {
505 for (i = 0; i < subspace_dim; i++) {
506 directions[k][i] = 0;
507 }
508 }
509 if (dim != 2) {
510 /* use the first vectors in the eigenspace */
511 /* each direction points to its "principal axes" */
512 for (k = 0; k < dim; k++) {
513 directions[k][k] = 1;
514 }
515 } else {
516 /* for the frequent 2-D case we prefer iterative-PCA over PCA */
517 /* Note that we don't want to mix the Lap's eigenspace with the HDE */
518 /* in the computation since they have different scales */
519
520 directions[0][0] = 1; /* first pca projection vector */
521 if (!iterativePCA_1D(subspace, subspace_dim, n, directions[1])) {
522 for (k = 0; k < subspace_dim; k++) {
523 directions[1][k] = 0;
524 }
525 directions[1][1] = 1;
526 }
527 }
528
529 } else {
530 /* random initialization */
531 for (k = 0; k < dim; k++) {
532 for (i = 0; i < subspace_dim; i++) {
533 directions[k][i] = (double) rand() / RAND_MAX;
534 }
535 }
536 }
537
538 /* compute initial k-D layout */
539
540 for (k = 0; k < dim; k++) {
541 right_mult_with_vector_transpose(subspace, n, subspace_dim,
542 directions[k], coords[k]);
543 }
544
545 /*************************************************
546 ** compute restriction of the laplacian to subspace: **
547 *************************************************/
548
549 tmp_mat = NULL;
550 double **matrix = NULL;
551 mult_sparse_dense_mat_transpose(lap, subspace, n, subspace_dim,
552 &tmp_mat);
553 mult_dense_mat_d(subspace, tmp_mat, subspace_dim, n, subspace_dim, &matrix);
554 free(tmp_mat[0]);
555 free(tmp_mat);
556
557 /*************************************************
558 ** Layout optimization **
559 *************************************************/
560
561 b = gv_calloc(n, sizeof(double));
562 b_restricted = gv_calloc(subspace_dim, sizeof(double));
563 old_stress = compute_stress1(coords, distances, dim, n, exp);
564 for (converged = false, iterations = 0;
565 iterations < n_iterations && !converged; iterations++) {
566
567 /* Axis-by-axis optimization: */
568 for (k = 0; k < dim; k++) {
569 /* compute the vector b */
570 /* multiply on-the-fly with distance-based laplacian */
571 /* (for saving storage we don't construct this Lap explicitly) */
572 for (i = 0; i < n; i++) {
573 degree = 0;
574 b[i] = 0;
575 dist_list = distances[i].edist - 1;
576 edges = lap[i].edges;
577 ewgts = lap[i].ewgts;
578 for (size_t j = 1; j < lap[i].nedges; j++) {
579 node = edges[j];
580 dist_ij = distance_kD(coords, dim, i, node);
581 if (dist_ij > 1e-30) { /* skip zero distances */
582 L_ij = -ewgts[j] * dist_list[j] / dist_ij; /* L_ij=w_{ij}*d_{ij}/dist_{ij} */
583 degree -= L_ij;
584 b[i] += L_ij * coords[k][node];
585 }
586 }
587 b[i] += degree * coords[k][i];
588 }
589 right_mult_with_vector_d(subspace, subspace_dim, n, b,
590 b_restricted);
591 if (conjugate_gradient_d(matrix, directions[k], b_restricted,
592 subspace_dim, conj_tol, subspace_dim,
593 false)) {
594 iterations = -1;
595 goto finish0;
596 }
597 right_mult_with_vector_transpose(subspace, n, subspace_dim,
598 directions[k], coords[k]);
599 }
600
601 if (iterations % 2 == 0) { // check for convergence each two iterations
602 new_stress = compute_stress1(coords, distances, dim, n, exp);
603 converged = fabs(new_stress - old_stress) / (new_stress + 1e-10) < Epsilon;
604 old_stress = new_stress;
605 }
606 }
607finish0:
608 free(b_restricted);
609 free(b);
610
611 if (reweight_graph) {
612 restore_old_weights(graph, n, old_weights);
613 }
614
615 for (i = 0; i < n; i++) {
616 if (distances[i].free_mem) {
617 free(distances[i].edges);
618 free(distances[i].edist);
619 }
620 }
621
622 free(distances);
623 free(lap[0].edges);
624 free(lap[0].ewgts);
625 free(lap);
626 free(CenterIndex);
627 free(invCenterIndex);
628 free(directions[0]);
629 free(directions);
630 if (matrix != NULL) {
631 free(matrix[0]);
632 free(matrix);
633 }
634 free(subspace[0]);
635 free(subspace);
636
637 return iterations;
638}
639
640/* compute_weighted_apsp_packed:
641 * Edge lengths can be any float > 0
642 */
644{
645 int i, j, count;
646 float *Dij = gv_calloc(n * (n + 1) / 2, sizeof(float));
647
648 float *Di = gv_calloc(n, sizeof(float));
649
650 count = 0;
651 for (i = 0; i < n; i++) {
652 dijkstra_f(i, graph, n, Di);
653 for (j = i; j < n; j++) {
654 Dij[count++] = Di[j];
655 }
656 }
657 free(Di);
658 return Dij;
659}
660
661
663float *mdsModel(vtx_data * graph, int nG)
664{
665 int i, j;
666 float *Dij;
667 int shift = 0;
668 double delta = 0.0;
669
670 if (graph->ewgts == NULL)
671 return 0;
672
673 /* first, compute shortest paths to fill in non-edges */
675
676 /* then, replace edge entries will user-supplied len */
677 for (i = 0; i < nG; i++) {
678 shift += i;
679 for (size_t e = 1; e < graph[i].nedges; e++) {
680 j = graph[i].edges[e];
681 if (j < i)
682 continue;
683 delta += fabsf(Dij[i * nG + j - shift] - graph[i].ewgts[e]);
684 Dij[i * nG + j - shift] = graph[i].ewgts[e];
685 }
686 }
687 if (Verbose) {
688 fprintf(stderr, "mdsModel: delta = %f\n", delta);
689 }
690 return Dij;
691}
692
695{
696 int i, j, count;
697 float *Dij = gv_calloc(n * (n + 1) / 2, sizeof(float));
698
699 DistType *Di = gv_calloc(n, sizeof(DistType));
700
701 count = 0;
702 for (i = 0; i < n; i++) {
703 bfs(i, graph, n, Di);
704 for (j = i; j < n; j++) {
705 Dij[count++] = (float)Di[j];
706 }
707 }
708 free(Di);
709 return Dij;
710}
711
713 /* compute all-pairs-shortest-path-length while weighting the graph */
714 /* so high-degree nodes are distantly located */
715
716 float *Dij;
717 int i;
718 float *old_weights = graph[0].ewgts;
719 size_t nedges = 0;
720 size_t deg_i, deg_j;
721 int neighbor;
722
723 for (i = 0; i < n; i++) {
724 nedges += graph[i].nedges;
725 }
726
727 float *weights = gv_calloc(nedges, sizeof(float));
728 int *vtx_vec = gv_calloc(n, sizeof(int));
729
730 if (graph->ewgts) {
731 for (i = 0; i < n; i++) {
733 deg_i = graph[i].nedges - 1;
734 for (size_t j = 1; j <= deg_i; j++) {
735 neighbor = graph[i].edges[j];
736 deg_j = graph[neighbor].nedges - 1;
737 weights[j] = fmaxf((float)(deg_i + deg_j -
738 2 * common_neighbors(graph, neighbor, vtx_vec)), graph[i].ewgts[j]);
739 }
740 empty_neighbors_vec(graph, i, vtx_vec);
741 graph[i].ewgts = weights;
742 weights += graph[i].nedges;
743 }
745 } else {
746 for (i = 0; i < n; i++) {
747 graph[i].ewgts = weights;
749 deg_i = graph[i].nedges - 1;
750 for (size_t j = 1; j <= deg_i; j++) {
751 neighbor = graph[i].edges[j];
752 deg_j = graph[neighbor].nedges - 1;
753 weights[j] =
754 (float)(deg_i + deg_j - 2 * common_neighbors(graph, neighbor, vtx_vec));
755 }
756 empty_neighbors_vec(graph, i, vtx_vec);
757 weights += graph[i].nedges;
758 }
759 Dij = compute_apsp_packed(graph, n);
760 }
761
762 free(vtx_vec);
763 free(graph[0].ewgts);
764 graph[0].ewgts = NULL;
765 if (old_weights != NULL) {
766 for (i = 0; i < n; i++) {
767 graph[i].ewgts = old_weights;
768 old_weights += graph[i].nedges;
769 }
770 }
771 return Dij;
772}
773
774/* Accumulator type for diagonal of Laplacian. Needs to be as large
775 * as possible. Use long double; configure to double if necessary.
776 */
777#define DegType long double
778
780int stress_majorization_kD_mkernel(vtx_data * graph, /* Input graph in sparse representation */
781 int n, /* Number of nodes */
782 double **d_coords, /* coordinates of nodes (output layout) */
783 node_t ** nodes, /* original nodes */
784 int dim, /* dimensionality of layout */
785 int opts, /* options */
786 int model, /* model */
787 int maxi /* max iterations */
788 )
789{
790 int iterations; /* output: number of iteration of the process */
791
792 double conj_tol = tolerance_cg; /* tolerance of Conjugate Gradient */
793 float *Dij = NULL;
794 int i, j, k;
795 float **coords = NULL;
796 float *f_storage = NULL;
797 float constant_term;
798 int count;
799 DegType degree;
800 int lap_length;
801 float *lap2 = NULL;
802 DegType *degrees = NULL;
803 int step;
804 float val;
805 double old_stress, new_stress;
806 bool converged;
807 float **b = NULL;
808 float *tmp_coords = NULL;
809 float *dist_accumulator = NULL;
810 float *lap1 = NULL;
811 int smart_ini = opts & opt_smart_init;
812 int exp = opts & opt_exp_flag;
813 int len;
814 int havePinned; /* some node is pinned */
815
816 /*************************************************
817 ** Computation of full, dense, unrestricted k-D **
818 ** stress minimization by majorization **
819 *************************************************/
820
821 /****************************************************
822 ** Compute the all-pairs-shortest-distances matrix **
823 ****************************************************/
824
825 if (maxi < 0)
826 return 0;
827
828 if (Verbose)
829 start_timer();
830
831 if (model == MODEL_SUBSET) {
832 /* weight graph to separate high-degree nodes */
833 /* and perform slower Dijkstra-based computation */
834 if (Verbose)
835 fprintf(stderr, "Calculating subset model");
837 } else if (model == MODEL_CIRCUIT) {
838 Dij = circuitModel(graph, n);
839 if (!Dij) {
841 "graph is disconnected. Hence, the circuit model\n");
843 "is undefined. Reverting to the shortest path model.\n");
844 }
845 } else if (model == MODEL_MDS) {
846 if (Verbose)
847 fprintf(stderr, "Calculating MDS model");
848 Dij = mdsModel(graph, n);
849 }
850 if (!Dij) {
851 if (Verbose)
852 fprintf(stderr, "Calculating shortest paths");
853 if (graph->ewgts)
855 else
856 Dij = compute_apsp_packed(graph, n);
857 }
858
859 if (Verbose) {
860 fprintf(stderr, ": %.2f sec\n", elapsed_sec());
861 fprintf(stderr, "Setting initial positions");
862 start_timer();
863 }
864
865 /**************************
866 ** Layout initialization **
867 **************************/
868
869 if (smart_ini && n > 1) {
870 havePinned = 0;
871 /* optimize layout quickly within subspace */
872 /* perform at most 50 iterations within 30-D subspace to
873 get an estimate */
875 d_coords, dim, smart_ini, exp,
876 model == MODEL_SUBSET, 50,
877 num_pivots_stress) < 0) {
878 iterations = -1;
879 goto finish1;
880 }
881
882 for (i = 0; i < dim; i++) {
883 /* for numerical stability, scale down layout */
884 double max = 1;
885 for (j = 0; j < n; j++) {
886 if (fabs(d_coords[i][j]) > max) {
887 max = fabs(d_coords[i][j]);
888 }
889 }
890 for (j = 0; j < n; j++) {
891 d_coords[i][j] /= max;
892 }
893 /* add small random noise */
894 for (j = 0; j < n; j++) {
895 d_coords[i][j] += 1e-6 * (drand48() - 0.5);
896 }
897 orthog1(n, d_coords[i]);
898 }
899 } else {
900 havePinned = initLayout(n, dim, d_coords, nodes);
901 }
902 if (Verbose)
903 fprintf(stderr, ": %.2f sec", elapsed_sec());
904 if (n == 1 || maxi == 0) {
905 free(Dij);
906 return 0;
907 }
908
909 if (Verbose) {
910 fprintf(stderr, ": %.2f sec\n", elapsed_sec());
911 fprintf(stderr, "Setting up stress function");
912 start_timer();
913 }
914 coords = gv_calloc(dim, sizeof(float *));
915 f_storage = gv_calloc(dim * n, sizeof(float));
916 for (i = 0; i < dim; i++) {
917 coords[i] = f_storage + i * n;
918 for (j = 0; j < n; j++) {
919 coords[i][j] = (float)d_coords[i][j];
920 }
921 }
922
923 /* compute constant term in stress sum */
924 /* which is \sum_{i<j} w_{ij}d_{ij}^2 */
925 assert(exp == 1 || exp == 2);
926 constant_term = (float)n * (n - 1) / 2;
927
928 /**************************
929 ** Laplacian computation **
930 **************************/
931
932 lap_length = n * (n + 1) / 2;
933 lap2 = Dij;
934 if (exp == 2) {
935 square_vec(lap_length, lap2);
936 }
937 /* compute off-diagonal entries */
938 invert_vec(lap_length, lap2);
939
940 /* compute diagonal entries */
941 count = 0;
942 degrees = gv_calloc(n, sizeof(DegType));
943 for (i = 0; i < n - 1; i++) {
944 degree = 0;
945 count++; /* skip main diag entry */
946 for (j = 1; j < n - i; j++, count++) {
947 val = lap2[count];
948 degree += val;
949 degrees[i + j] -= val;
950 }
951 degrees[i] -= degree;
952 }
953 for (step = n, count = 0, i = 0; i < n; i++, count += step, step--) {
954 lap2[count] = degrees[i];
955 }
956
957 /*************************
958 ** Layout optimization **
959 *************************/
960
961 b = gv_calloc(dim, sizeof(float *));
962 b[0] = gv_calloc(dim * n, sizeof(float));
963 for (k = 1; k < dim; k++) {
964 b[k] = b[0] + k * n;
965 }
966
967 tmp_coords = gv_calloc(n, sizeof(float));
968 dist_accumulator = gv_calloc(n, sizeof(float));
969 lap1 = gv_calloc(lap_length, sizeof(float));
970
971
972 old_stress = DBL_MAX; // at least one iteration
973 if (Verbose) {
974 fprintf(stderr, ": %.2f sec\n", elapsed_sec());
975 fprintf(stderr, "Solving model: ");
976 start_timer();
977 }
978
979 for (converged = false, iterations = 0;
980 iterations < maxi && !converged; iterations++) {
981
982 /* First, construct Laplacian of 1/(d_ij*|p_i-p_j|) */
983 /* set_vector_val(n, 0, degrees); */
984 memset(degrees, 0, n * sizeof(DegType));
985 if (exp == 2) {
986 sqrt_vecf(lap_length, lap2, lap1);
987 }
988 for (count = 0, i = 0; i < n - 1; i++) {
989 len = n - i - 1;
990 /* init 'dist_accumulator' with zeros */
991 set_vector_valf(len, 0, dist_accumulator);
992
993 /* put into 'dist_accumulator' all squared distances between 'i' and 'i'+1,...,'n'-1 */
994 for (k = 0; k < dim; k++) {
995 size_t x;
996 for (x = 0; x < (size_t)len; ++x) {
997 float tmp = coords[k][i] + -1.0f * (coords[k] + i + 1)[x];
998 dist_accumulator[x] += tmp * tmp;
999 }
1000 }
1001
1002 /* convert to 1/d_{ij} */
1003 invert_sqrt_vec(len, dist_accumulator);
1004 /* detect overflows */
1005 for (j = 0; j < len; j++) {
1006 if (dist_accumulator[j] >= FLT_MAX || dist_accumulator[j] < 0) {
1007 dist_accumulator[j] = 0;
1008 }
1009 }
1010
1011 count++; /* save place for the main diagonal entry */
1012 degree = 0;
1013 if (exp == 2) {
1014 for (j = 0; j < len; j++, count++) {
1015 val = lap1[count] *= dist_accumulator[j];
1016 degree += val;
1017 degrees[i + j + 1] -= val;
1018 }
1019 } else {
1020 for (j = 0; j < len; j++, count++) {
1021 val = lap1[count] = dist_accumulator[j];
1022 degree += val;
1023 degrees[i + j + 1] -= val;
1024 }
1025 }
1026 degrees[i] -= degree;
1027 }
1028 for (step = n, count = 0, i = 0; i < n; i++, count += step, step--) {
1029 lap1[count] = degrees[i];
1030 }
1031
1032 /* Now compute b[] */
1033 for (k = 0; k < dim; k++) {
1034 /* b[k] := lap1*coords[k] */
1035 right_mult_with_vector_ff(lap1, n, coords[k], b[k]);
1036 }
1037
1038
1039 /* compute new stress */
1040 /* remember that the Laplacians are negated, so we subtract instead of add and vice versa */
1041 new_stress = 0;
1042 for (k = 0; k < dim; k++) {
1043 new_stress += vectors_inner_productf(n, coords[k], b[k]);
1044 }
1045 new_stress *= 2;
1046 new_stress += constant_term; /* only after mult by 2 */
1047 for (k = 0; k < dim; k++) {
1048 right_mult_with_vector_ff(lap2, n, coords[k], tmp_coords);
1049 new_stress -= vectors_inner_productf(n, coords[k], tmp_coords);
1050 }
1051 /* Invariant: old_stress > 0. In theory, old_stress >= new_stress
1052 * but we use fabs in case of numerical error.
1053 */
1054 {
1055 double diff = old_stress - new_stress;
1056 double change = fabs(diff);
1057 converged = change / old_stress < Epsilon || new_stress < Epsilon;
1058 }
1059 old_stress = new_stress;
1060
1061 for (k = 0; k < dim; k++) {
1062 node_t *np;
1063 if (havePinned) {
1064 copy_vectorf(n, coords[k], tmp_coords);
1065 if (conjugate_gradient_mkernel(lap2, tmp_coords, b[k], n,
1066 conj_tol, n) < 0) {
1067 iterations = -1;
1068 goto finish1;
1069 }
1070 for (i = 0; i < n; i++) {
1071 np = nodes[i];
1072 if (!isFixed(np))
1073 coords[k][i] = tmp_coords[i];
1074 }
1075 } else {
1076 if (conjugate_gradient_mkernel(lap2, coords[k], b[k], n,
1077 conj_tol, n) < 0) {
1078 iterations = -1;
1079 goto finish1;
1080 }
1081 }
1082 }
1083 if (Verbose && iterations % 5 == 0) {
1084 fprintf(stderr, "%.3f ", new_stress);
1085 if ((iterations + 5) % 50 == 0)
1086 fprintf(stderr, "\n");
1087 }
1088 }
1089 if (Verbose) {
1090 fprintf(stderr, "\nfinal e = %f %d iterations %.2f sec\n",
1091 compute_stressf(coords, lap2, dim, n, exp),
1092 iterations, elapsed_sec());
1093 }
1094
1095 for (i = 0; i < dim; i++) {
1096 for (j = 0; j < n; j++) {
1097 d_coords[i][j] = coords[i][j];
1098 }
1099 }
1100finish1:
1101 free(f_storage);
1102 free(coords);
1103
1104 free(lap2);
1105 if (b) {
1106 free(b[0]);
1107 free(b);
1108 }
1109 free(tmp_coords);
1110 free(dist_accumulator);
1111 free(degrees);
1112 free(lap1);
1113 return iterations;
1114}
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:1539
int conjugate_gradient_d(double **A, double *x, double *b, int n, double tol, int max_iterations, bool ortho1)
Definition conjgrad.c:92
int conjugate_gradient_mkernel(float *A, float *x, float *b, int n, double tol, int max_iterations)
Definition conjgrad.c:161
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:951
#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:268
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, const double *vector, double *restrict result)
Definition matrix_ops.c:305
void right_mult_with_vector_transpose(double **matrix, int dim1, int dim2, double *vector, double *result)
Definition matrix_ops.c:290
void invert_vec(int n, float *vec)
Definition matrix_ops.c:420
void mult_sparse_dense_mat_transpose(vtx_data *A, double **B, int dim1, int dim2, float ***CC)
Definition matrix_ops.c:157
void invert_sqrt_vec(int n, float *vec)
Definition matrix_ops.c:438
void set_vector_valf(int n, float val, float *result)
Definition matrix_ops.c:398
void orthog1(int n, double *vec)
Definition matrix_ops.c:201
void copy_vectorf(int n, float *source, float *dest)
Definition matrix_ops.c:382
void sqrt_vecf(int n, float *source, float *target)
Definition matrix_ops.c:429
void mult_dense_mat_d(double **A, float **B, int dim1, int dim2, int dim3, double ***CC)
Definition matrix_ops.c:131
void square_vec(int n, float *vec)
Definition matrix_ops.c:413
void right_mult_with_vector_ff(const float *packed_matrix, int n, const float *vector, float *restrict result)
Definition matrix_ops.c:339
double vectors_inner_productf(int n, float *vector1, float *vector2)
Definition matrix_ops.c:388
#define delta
Definition maze.c:138
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:712
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:780
#define DegType
Definition stress.c:777
static float * compute_weighted_apsp_packed(vtx_data *graph, int n)
Definition stress.c:643
static int sparse_stress_subspace_majorization_kD(vtx_data *graph, int n, double **coords, int dim, int smart_ini, int exp, int reweight_graph, int n_iterations, int num_centers)
Definition stress.c: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:663
float * compute_apsp_packed(vtx_data *graph, int n)
assumes integral weights > 0
Definition stress.c:694
static double compute_stress1(double **coords, dist_data *distances, int dim, int n, int exp)
Definition stress.c: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