Graphviz 15.1.1~dev.20260630.1303
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 <time.h>
45#include <stdio.h>
46#include <float.h>
47#include <neatogen/stress.h>
48#include <neatogen/dijkstra.h>
49#include <neatogen/bfs.h>
50#include <neatogen/matrix_ops.h>
51#include <neatogen/kkutils.h>
52#include <neatogen/conjgrad.h>
53#include <vpsc/csolve_VPSC.h>
56#include <neatogen/matrix_ops.h>
57
58#define localConstrMajorIterations 1000
59
60int stress_majorization_cola(vtx_data * graph, /* Input graph in sparse representation */
61 int n, /* Number of nodes */
62 double **d_coords, /* Coordinates of nodes (output layout) */
63 node_t ** nodes, /* Original nodes */
64 int dim, /* Dimensionality of layout */
65 int model, /* difference model */
66 int maxi, /* max iterations */
67 ipsep_options * opt)
68{
69 int iterations = 0; /* Output: number of iteration of the process */
70
71 /*************************************************
72 ** Computation of full, dense, unrestricted k-D **
73 ** stress minimization by majorization **
74 ** This function imposes HIERARCHY CONSTRAINTS **
75 *************************************************/
76
77 int k;
78 float *lap1 = NULL;
79 float *dist_accumulator = NULL;
80 float *tmp_coords = NULL;
81 float **b = NULL;
82 double *degrees = NULL;
83 float *lap2 = NULL;
84 int lap_length;
85 float *f_storage = NULL;
86 float **coords = NULL;
87 int orig_n = n;
88
89 CMajEnvVPSC *cMajEnvHor = NULL;
90 CMajEnvVPSC *cMajEnvVrt = NULL;
91 double y_0;
92 int length;
93 DistType diameter;
94 float *Dij = NULL;
95 float constant_term;
96 int count;
97 double degree;
98 int step;
99 float val;
100 double old_stress, new_stress = 0;
101 bool converged;
102 int len;
103 double nsizeScale = 0;
104 float maxEdgeLen = 0;
105 double max = 1;
106
107 initLayout(n, dim, d_coords, nodes);
108 if (n == 1)
109 return 0;
110
111 for (int i = 0; i < n; i++) {
112 for (size_t j = 1; j < graph[i].nedges; j++) {
113 maxEdgeLen = MAX(graph[i].ewgts[j], maxEdgeLen);
114 }
115 }
116
117 /****************************************************
118 ** Compute the all-pairs-shortest-distances matrix **
119 ****************************************************/
120
121 if (maxi == 0)
122 return iterations;
123
124 if (Verbose)
125 start_timer();
126
127 if (model == MODEL_SUBSET) {
128 /* weight graph to separate high-degree nodes */
129 /* and perform slower Dijkstra-based computation */
130 if (Verbose)
131 fprintf(stderr, "Calculating subset model");
133 } else if (model == MODEL_CIRCUIT) {
134 Dij = circuitModel(graph, n);
135 if (!Dij) {
137 "graph is disconnected. Hence, the circuit model\n");
139 "is undefined. Reverting to the shortest path model.\n");
140 }
141 } else if (model == MODEL_MDS) {
142 if (Verbose)
143 fprintf(stderr, "Calculating MDS model");
144 Dij = mdsModel(graph, n);
145 }
146 if (!Dij) {
147 if (Verbose)
148 fprintf(stderr, "Calculating shortest paths");
149 Dij = compute_apsp_packed(graph, n);
150 }
151 if (Verbose) {
152 fprintf(stderr, ": %.2f sec\n", elapsed_sec());
153 fprintf(stderr, "Setting initial positions");
154 start_timer();
155 }
156
157 diameter = -1;
158 length = n + n * (n - 1) / 2;
159 for (int i = 0; i < length; i++) {
160 if (Dij[i] > diameter) {
161 diameter = (int) Dij[i];
162 }
163 }
164
165 /* for numerical stability, scale down layout */
166 /* No Jiggling, might conflict with constraints */
167 for (int i = 0; i < dim; i++) {
168 for (int j = 0; j < n; j++) {
169 max = fmax(max, fabs(d_coords[i][j]));
170 }
171 }
172 for (int i = 0; i < dim; i++) {
173 for (int j = 0; j < n; j++) {
174 d_coords[i][j] *= 10 / max;
175 }
176 }
177
178 /**************************
179 ** Layout initialization **
180 **************************/
181
182 for (int i = 0; i < dim; i++) {
183 orthog1(n, d_coords[i]);
184 }
185
186 /* for the y-coords, don't center them, but translate them so y[0]=0 */
187 y_0 = d_coords[1][0];
188 for (int i = 0; i < n; i++) {
189 d_coords[1][i] -= y_0;
190 }
191 if (Verbose)
192 fprintf(stderr, ": %.2f sec", elapsed_sec());
193
194 /**************************
195 ** Laplacian computation **
196 **************************/
197
198 lap2 = Dij;
199 lap_length = n + n * (n - 1) / 2;
200 square_vec(lap_length, lap2);
201 /* compute off-diagonal entries */
202 invert_vec(lap_length, lap2);
203
204 if (opt->clusters.nclusters > 0) {
205 const size_t nn = n + opt->clusters.nclusters * 2;
206 const size_t clap_length = nn + nn * (nn - 1) / 2;
207 float *clap = gv_calloc(clap_length, sizeof(float));
208 int c0, c1;
209 float v;
210 c0 = c1 = 0;
211 for (size_t i = 0; i < nn; i++) {
212 for (int j = 0; j < nn - i; j++) {
213 if (i < n && j < n - i) {
214 v = lap2[c0++];
215 } else {
216 /* v=j==1?i%2:0; */
217 if (j == 1 && i % 2 == 1) {
218 v = maxEdgeLen;
219 v *= v;
220 if (v > 0.01f) {
221 v = 1.0f / v;
222 }
223 } else
224 v = 0;
225 }
226 clap[c1++] = v;
227 }
228 }
229 free(lap2);
230 lap2 = clap;
231 assert(nn <= INT_MAX);
232 n = (int)nn;
233 assert(clap_length <= INT_MAX);
234 lap_length = (int)clap_length;
235 }
236 /* compute diagonal entries */
237 count = 0;
238 degrees = gv_calloc(n, sizeof(double));
239 set_vector_val(n, 0, degrees);
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 set_vector_val(n, 0, degrees);
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:946
Agraph_t * graph(char *name)
Definition gv.cpp:34
void invert_vec(int n, float *vec)
Definition matrix_ops.c:502
void invert_sqrt_vec(int n, float *vec)
Definition matrix_ops.c:522
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:477
void orthog1(int n, double *vec)
Definition matrix_ops.c:229
void sqrt_vecf(int n, float *source, float *target)
Definition matrix_ops.c:512
void set_vector_val(int n, double val, double *result)
Definition matrix_ops.c:470
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:494
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