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