Graphviz 16.0.1~dev.20260815.2250
Loading...
Searching...
No Matches
constrained_majorization_ipsep.c
Go to the documentation of this file.
1
17/**********************************************************
18 * Based on constrained_majorization.c
19 *
20 * Perform stress majorization subject
21 * to separation constraints, for background see the paper:
22 * "IPSep-CoLa: An Incremental Procedure for Separation Constraint Layout of Graphs"
23 * by Tim Dwyer, Yehuda Koren and Kim Marriott
24 *
25 * Available separation constraints so far are:
26 * o Directed edge constraints
27 * o Node non-overlap constraints
28 * o Cluster containment constraints
29 * o Cluster/node non-overlap constraints
30 *
31 * Tim Dwyer, 2006
32 **********************************************************/
33
34#include "config.h"
35
36#include <assert.h>
37#include <limits.h>
38#include <neatogen/digcola.h>
39#include <stdbool.h>
40#include <util/alloc.h>
41#ifdef IPSEPCOLA
42#include <math.h>
43#include <stdlib.h>
44#include <string.h>
45#include <time.h>
46#include <stdio.h>
47#include <float.h>
48#include <neatogen/stress.h>
49#include <neatogen/dijkstra.h>
50#include <neatogen/bfs.h>
51#include <neatogen/matrix_ops.h>
52#include <neatogen/kkutils.h>
53#include <neatogen/conjgrad.h>
54#include <vpsc/csolve_VPSC.h>
57#include <neatogen/matrix_ops.h>
58
59#define localConstrMajorIterations 1000
60
61int stress_majorization_cola(vtx_data * graph, /* Input graph in sparse representation */
62 int n, /* Number of nodes */
63 double **d_coords, /* Coordinates of nodes (output layout) */
64 node_t ** nodes, /* Original nodes */
65 int dim, /* Dimensionality of layout */
66 int model, /* difference model */
67 int maxi, /* max iterations */
68 ipsep_options * opt)
69{
70 int iterations = 0; /* Output: number of iteration of the process */
71
72 /*************************************************
73 ** Computation of full, dense, unrestricted k-D **
74 ** stress minimization by majorization **
75 ** This function imposes HIERARCHY CONSTRAINTS **
76 *************************************************/
77
78 int k;
79 float *lap1 = NULL;
80 float *dist_accumulator = NULL;
81 float *tmp_coords = NULL;
82 float **b = NULL;
83 double *degrees = NULL;
84 float *lap2 = NULL;
85 int lap_length;
86 float *f_storage = NULL;
87 float **coords = NULL;
88 int orig_n = n;
89
90 CMajEnvVPSC *cMajEnvHor = NULL;
91 CMajEnvVPSC *cMajEnvVrt = NULL;
92 double y_0;
93 int length;
94 DistType diameter;
95 float *Dij = NULL;
96 float constant_term;
97 int count;
98 double degree;
99 int step;
100 float val;
101 double old_stress, new_stress = 0;
102 bool converged;
103 int len;
104 double nsizeScale = 0;
105 float maxEdgeLen = 0;
106 double max = 1;
107
108 initLayout(n, dim, d_coords, nodes);
109 if (n == 1)
110 return 0;
111
112 for (int i = 0; i < n; i++) {
113 for (size_t j = 1; j < graph[i].nedges; j++) {
114 maxEdgeLen = MAX(graph[i].ewgts[j], maxEdgeLen);
115 }
116 }
117
118 /****************************************************
119 ** Compute the all-pairs-shortest-distances matrix **
120 ****************************************************/
121
122 if (maxi == 0)
123 return iterations;
124
125 if (Verbose)
126 start_timer();
127
128 if (model == MODEL_SUBSET) {
129 /* weight graph to separate high-degree nodes */
130 /* and perform slower Dijkstra-based computation */
131 if (Verbose)
132 fprintf(stderr, "Calculating subset model");
134 } else if (model == MODEL_CIRCUIT) {
135 Dij = circuitModel(graph, n);
136 if (!Dij) {
138 "graph is disconnected. Hence, the circuit model\n");
140 "is undefined. Reverting to the shortest path model.\n");
141 }
142 } else if (model == MODEL_MDS) {
143 if (Verbose)
144 fprintf(stderr, "Calculating MDS model");
145 Dij = mdsModel(graph, n);
146 }
147 if (!Dij) {
148 if (Verbose)
149 fprintf(stderr, "Calculating shortest paths");
150 Dij = compute_apsp_packed(graph, n);
151 }
152 if (Verbose) {
153 fprintf(stderr, ": %.2f sec\n", elapsed_sec());
154 fprintf(stderr, "Setting initial positions");
155 start_timer();
156 }
157
158 diameter = -1;
159 length = n + n * (n - 1) / 2;
160 for (int i = 0; i < length; i++) {
161 if (Dij[i] > diameter) {
162 diameter = (int) Dij[i];
163 }
164 }
165
166 /* for numerical stability, scale down layout */
167 /* No Jiggling, might conflict with constraints */
168 for (int i = 0; i < dim; i++) {
169 for (int j = 0; j < n; j++) {
170 max = fmax(max, fabs(d_coords[i][j]));
171 }
172 }
173 for (int i = 0; i < dim; i++) {
174 for (int j = 0; j < n; j++) {
175 d_coords[i][j] *= 10 / max;
176 }
177 }
178
179 /**************************
180 ** Layout initialization **
181 **************************/
182
183 for (int i = 0; i < dim; i++) {
184 orthog1(n, d_coords[i]);
185 }
186
187 /* for the y-coords, don't center them, but translate them so y[0]=0 */
188 y_0 = d_coords[1][0];
189 for (int i = 0; i < n; i++) {
190 d_coords[1][i] -= y_0;
191 }
192 if (Verbose)
193 fprintf(stderr, ": %.2f sec", elapsed_sec());
194
195 /**************************
196 ** Laplacian computation **
197 **************************/
198
199 lap2 = Dij;
200 lap_length = n + n * (n - 1) / 2;
201 square_vec(lap_length, lap2);
202 /* compute off-diagonal entries */
203 invert_vec(lap_length, lap2);
204
205 if (opt->clusters.nclusters > 0) {
206 const size_t nn = n + opt->clusters.nclusters * 2;
207 const size_t clap_length = nn + nn * (nn - 1) / 2;
208 float *clap = gv_calloc(clap_length, sizeof(float));
209 int c0, c1;
210 float v;
211 c0 = c1 = 0;
212 for (size_t i = 0; i < nn; i++) {
213 for (int j = 0; j < nn - i; j++) {
214 if (i < n && j < n - i) {
215 v = lap2[c0++];
216 } else {
217 /* v=j==1?i%2:0; */
218 if (j == 1 && i % 2 == 1) {
219 v = maxEdgeLen;
220 v *= v;
221 if (v > 0.01f) {
222 v = 1.0f / v;
223 }
224 } else
225 v = 0;
226 }
227 clap[c1++] = v;
228 }
229 }
230 free(lap2);
231 lap2 = clap;
232 assert(nn <= INT_MAX);
233 n = (int)nn;
234 assert(clap_length <= INT_MAX);
235 lap_length = (int)clap_length;
236 }
237 /* compute diagonal entries */
238 count = 0;
239 degrees = gv_calloc(n, sizeof(double));
240 for (int i = 0; i < n - 1; i++) {
241 degree = 0;
242 count++; /* skip main diag entry */
243 for (int j = 1; j < n - i; j++, count++) {
244 val = lap2[count];
245 degree += val;
246 degrees[i + j] -= val;
247 }
248 degrees[i] -= degree;
249 }
250 int i;
251 for (step = n, count = 0, i = 0; i < n; i++, count += step, step--) {
252 lap2[count] = (float) degrees[i];
253 }
254
255 coords = gv_calloc(dim, sizeof(float *));
256 f_storage = gv_calloc(dim * n, sizeof(float));
257 for (i = 0; i < dim; i++) {
258 coords[i] = f_storage + i * n;
259 for (int j = 0; j < n; j++) {
260 coords[i][j] = j < orig_n ? (float)d_coords[i][j] : 0;
261 }
262 }
263
264 /* compute constant term in stress sum
265 * which is \sum_{i<j} w_{ij}d_{ij}^2
266 */
267 constant_term = (float) (n * (n - 1) / 2);
268
269 /*************************
270 ** Layout optimization **
271 *************************/
272
273 b = gv_calloc(dim, sizeof(float *));
274 b[0] = gv_calloc(dim * n, sizeof(float));
275 for (k = 1; k < dim; k++) {
276 b[k] = b[0] + k * n;
277 }
278
279 tmp_coords = gv_calloc(n, sizeof(float));
280 dist_accumulator = gv_calloc(n, sizeof(float));
281
282 old_stress = DBL_MAX; /* at least one iteration */
283
284 if ((cMajEnvHor = initCMajVPSC(n, lap2, graph, opt, 0)) == NULL) {
285 iterations = -1;
286 goto finish;
287 }
288 if ((cMajEnvVrt = initCMajVPSC(n, lap2, graph, opt, opt->diredges)) == NULL) {
289 iterations = -1;
290 goto finish;
291 }
292
293 lap1 = gv_calloc(lap_length, sizeof(float));
294
295 for (converged = false, iterations = 0;
296 iterations < maxi && !converged; iterations++) {
297
298 /* First, construct Laplacian of 1/(d_ij*|p_i-p_j|) */
299 memset(degrees, 0, (size_t)n * sizeof(degrees[0]));
300 sqrt_vecf(lap_length, lap2, lap1);
301 for (count = 0, i = 0; i < n - 1; i++) {
302 len = n - i - 1;
303 /* init 'dist_accumulator' with zeros */
304 set_vector_valf(n, 0, dist_accumulator);
305
306 /* put into 'dist_accumulator' all squared distances
307 * between 'i' and 'i'+1,...,'n'-1
308 */
309 for (k = 0; k < dim; k++) {
310 set_vector_valf(len, coords[k][i], tmp_coords);
311 vectors_mult_additionf(len, tmp_coords, -1, coords[k] + i + 1);
312 square_vec(len, tmp_coords);
313 vectors_additionf(len, tmp_coords, dist_accumulator, dist_accumulator);
314 }
315
316 /* convert to 1/d_{ij} */
317 invert_sqrt_vec(len, dist_accumulator);
318 /* detect overflows */
319 for (int j = 0; j < len; j++) {
320 if (dist_accumulator[j] >= FLT_MAX || dist_accumulator[j] < 0) {
321 dist_accumulator[j] = 0;
322 }
323 }
324
325 count++; /* save place for the main diagonal entry */
326 degree = 0;
327 for (int j = 0; j < len; j++, count++) {
328 val = lap1[count] *= dist_accumulator[j];
329 degree += val;
330 degrees[i + j + 1] -= val;
331 }
332 degrees[i] -= degree;
333 }
334 for (step = n, count = 0, i = 0; i < n; i++, count += step, step--) {
335 lap1[count] = (float) degrees[i];
336 }
337
338 /* Now compute b[] (L^(X(t))*X(t)) */
339 for (k = 0; k < dim; k++) {
340 /* b[k] := lap1*coords[k] */
341 right_mult_with_vector_ff(lap1, n, coords[k], b[k]);
342 }
343
344 /* compute new stress
345 * remember that the Laplacians are negated, so we subtract
346 * instead of add and vice versa
347 */
348 new_stress = 0;
349 for (k = 0; k < dim; k++) {
350 new_stress += vectors_inner_productf(n, coords[k], b[k]);
351 }
352 new_stress *= 2;
353 new_stress += constant_term; /* only after mult by 2 */
354 for (k = 0; k < dim; k++) {
355 right_mult_with_vector_ff(lap2, n, coords[k], tmp_coords);
356 new_stress -= vectors_inner_productf(n, coords[k], tmp_coords);
357 }
358
359 /* check for convergence */
360 if (Verbose && (iterations % 1 == 0)) {
361 fprintf(stderr, "%.3f ", new_stress);
362 if (iterations % 10 == 0)
363 fprintf(stderr, "\n");
364 }
365 converged = new_stress < old_stress
366 && fabs(new_stress - old_stress) / fabs(old_stress + 1e-10) <
367 Epsilon;
368 /*converged = converged || (iterations>1 && new_stress>old_stress); */
369 /* in first iteration we allowed stress increase, which
370 * might result ny imposing constraints
371 */
372 old_stress = new_stress;
373
374 /* in determining non-overlap constraints we gradually scale up the
375 * size of nodes to avoid local minima
376 */
377 if ((iterations >= maxi - 1 || converged) && opt->noverlap == 1
378 && nsizeScale < 0.999) {
379 nsizeScale += 0.1;
380 if (Verbose)
381 fprintf(stderr, "nsizescale=%f,iterations=%d\n",
382 nsizeScale, iterations);
383 iterations = 0;
384 converged = false;
385 }
386
387
388 /* now we find the optimizer of trace(X'LX)+X'B by solving 'dim'
389 * system of equations, thereby obtaining the new coordinates.
390 * If we use the constraints (given by the var's: 'ordering',
391 * 'levels' and 'num_levels'), we cannot optimize
392 * trace(X'LX)+X'B by simply solving equations, but we have
393 * to use a quadratic programming solver
394 * note: 'lap2' is a packed symmetric matrix, that is its
395 * upper-triangular part is arranged in a vector row-wise
396 * also note: 'lap2' is really the negated laplacian (the
397 * laplacian is -'lap2')
398 */
399
400 if (opt->noverlap == 1 && nsizeScale > 0.001) {
401 generateNonoverlapConstraints(cMajEnvHor, nsizeScale, coords,
402 0,
403 nsizeScale >= 0.5,
404 opt);
405 }
406 if (cMajEnvHor->m > 0) {
407 constrained_majorization_vpsc(cMajEnvHor, b[0], coords[0],
408 localConstrMajorIterations);
409 } else {
410 /* if there are no constraints then use conjugate gradient
411 * optimisation which should be considerably faster
412 */
413 if (conjugate_gradient_mkernel(lap2, coords[0], b[0], n,
414 tolerance_cg, n) < 0) {
415 iterations = -1;
416 goto finish;
417 }
418 }
419 if (opt->noverlap == 1 && nsizeScale > 0.001) {
420 generateNonoverlapConstraints(cMajEnvVrt, nsizeScale, coords,
421 1, false, opt);
422 }
423 if (cMajEnvVrt->m > 0) {
424 if (constrained_majorization_vpsc(cMajEnvVrt, b[1], coords[1],
425 localConstrMajorIterations) < 0) {
426 iterations = -1;
427 goto finish;
428 }
429 } else {
430 conjugate_gradient_mkernel(lap2, coords[1], b[1], n,
431 tolerance_cg, n);
432 }
433 }
434 if (Verbose) {
435 fprintf(stderr, "\nfinal e = %f %d iterations %.2f sec\n",
436 new_stress, iterations, elapsed_sec());
437 }
438 deleteCMajEnvVPSC(cMajEnvHor);
439 deleteCMajEnvVPSC(cMajEnvVrt);
440
441 if (opt->noverlap == 2) {
442 /* fprintf(stderr, "Removing overlaps as post-process...\n"); */
443 removeoverlaps(orig_n, coords, opt);
444 }
445
446finish:
447 if (coords != NULL) {
448 for (i = 0; i < dim; i++) {
449 for (int j = 0; j < orig_n; j++) {
450 d_coords[i][j] = coords[i][j];
451 }
452 }
453 free(coords[0]);
454 free(coords);
455 }
456
457 if (b) {
458 free(b[0]);
459 free(b);
460 }
461 free(tmp_coords);
462 free(dist_accumulator);
463 free(degrees);
464 free(lap2);
465 free(lap1);
466
467 return iterations;
468}
469#endif /* IPSEPCOLA */
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 MAX(a, b)
Definition arith.h:33
static Extype_t length(Exid_t *rhs, Exdisc_t *disc)
Definition compile.c:1615
int conjugate_gradient_mkernel(float *A, float *x, float *b, int n, double tol, int max_iterations)
Definition conjgrad.c:162
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
Agraph_t * graph(char *name)
Definition gv.cpp:34
void invert_vec(int n, float *vec)
Definition matrix_ops.c:495
void invert_sqrt_vec(int n, float *vec)
Definition matrix_ops.c:515
void vectors_additionf(int n, float *vector1, float *vector2, float *result)
Definition matrix_ops.c:435
void set_vector_valf(int n, float val, float *result)
Definition matrix_ops.c:470
void orthog1(int n, double *vec)
Definition matrix_ops.c:229
void sqrt_vecf(int n, float *source, float *target)
Definition matrix_ops.c:505
void right_mult_with_vector_ff(float *packed_matrix, int n, float *vector, float *result)
Definition matrix_ops.c:402
void vectors_mult_additionf(int n, float *vector1, float alpha, float *vector2)
Definition matrix_ops.c:444
void square_vec(int n, float *vec)
Definition matrix_ops.c:487
double vectors_inner_productf(int n, float *vector1, float *vector2)
Definition matrix_ops.c:459
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
int DistType
Definition sparsegraph.h:39
float * compute_apsp_artificial_weights_packed(vtx_data *graph, int n)
Definition stress.c:714
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
#define tolerance_cg
Definition stress.h:21
double elapsed_sec(void)
Definition timing.c:23
void start_timer(void)
Definition timing.c:21