Graphviz 13.0.0~dev.20241220.2304
Loading...
Searching...
No Matches
constrained_majorization.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 <neatogen/digcola.h>
12#include <util/alloc.h>
13#ifdef DIGCOLA
14#include <math.h>
15#include <stdbool.h>
16#include <stdlib.h>
17#include <time.h>
18#include <stdio.h>
19#include <float.h>
20#include <neatogen/stress.h>
21#include <neatogen/dijkstra.h>
22#include <neatogen/bfs.h>
23#include <neatogen/matrix_ops.h>
24#include <neatogen/kkutils.h>
25#include <neatogen/conjgrad.h>
27#include <neatogen/matrix_ops.h>
28
29#define localConstrMajorIterations 15
30#define levels_sep_tol 1e-1
31
32int stress_majorization_with_hierarchy(vtx_data * graph, /* Input graph in sparse representation */
33 int n, /* Number of nodes */
34 double **d_coords, /* Coordinates of nodes (output layout) */
35 node_t ** nodes, /* Original nodes */
36 int dim, /* Dimemsionality of layout */
37 int opts, /* options */
38 int model, /* difference model */
39 int maxi, /* max iterations */
40 double levels_gap)
41{
42 int iterations = 0; /* Output: number of iteration of the process */
43
44 /*************************************************
45 ** Computation of full, dense, unrestricted k-D **
46 ** stress minimization by majorization **
47 ** This function imposes HIERARCHY CONSTRAINTS **
48 *************************************************/
49
50 int i, k;
51 bool directionalityExist = false;
52 float *lap1 = NULL;
53 float *dist_accumulator = NULL;
54 float *tmp_coords = NULL;
55 float **b = NULL;
56 double *degrees = NULL;
57 float *lap2 = NULL;
58 int lap_length;
59 float *f_storage = NULL;
60 float **coords = NULL;
61
62 double conj_tol = tolerance_cg; /* tolerance of Conjugate Gradient */
63 CMajEnv *cMajEnv = NULL;
64 double y_0;
65 int length;
66 int smart_ini = opts & opt_smart_init;
67 DistType diameter;
68 float *Dij = NULL;
69 /* to compensate noises, we never consider gaps smaller than 'abs_tol' */
70 double abs_tol = 1e-2;
71 /* Additionally, we never consider gaps smaller than 'abs_tol'*<avg_gap> */
72 double relative_tol = levels_sep_tol;
73 int *ordering = NULL, *levels = NULL;
74 float constant_term;
75 double degree;
76 int step;
77 float val;
78 double old_stress, new_stress;
79 bool converged;
80 int len;
81 int num_levels;
82
83 if (graph[0].edists != NULL) {
84 for (i = 0; i < n; i++) {
85 for (size_t j = 1; j < graph[i].nedges; j++) {
86 directionalityExist |= graph[i].edists[j] != 0;
87 }
88 }
89 }
90 if (!directionalityExist) {
92 d_coords, nodes, dim, opts,
93 model, maxi);
94 }
95
96 /******************************************************************
97 ** First, partition nodes into layers: These are our constraints **
98 ******************************************************************/
99
100 if (smart_ini) {
101 double *x;
102 double *y;
103 if (dim > 2) {
104 /* the dim==2 case is handled below */
106 d_coords + 1, nodes, dim - 1,
107 opts, model, 15) < 0)
108 return -1;
109 /* now copy the y-axis into the (dim-1)-axis */
110 for (i = 0; i < n; i++) {
111 d_coords[dim - 1][i] = d_coords[1][i];
112 }
113 }
114
115 x = d_coords[0];
116 y = d_coords[1];
117 if (compute_y_coords(graph, n, y, n)) {
118 iterations = -1;
119 goto finish;
120 }
121 if (compute_hierarchy(graph, n, abs_tol, relative_tol, y, &ordering,
122 &levels, &num_levels)) {
123 iterations = -1;
124 goto finish;
125 }
126 if (num_levels < 1) {
127 /* no hierarchy found, use faster algorithm */
128 free(levels);
130 d_coords, nodes, dim,
131 opts, model, maxi);
132 }
133
134 if (levels_gap > 0) {
135 /* ensure that levels are separated in the initial layout */
136 double displacement = 0;
137 int stop;
138 for (i = 0; i < num_levels; i++) {
139 displacement +=
140 MAX(0.0,
141 levels_gap - (y[ordering[levels[i]]] +
142 displacement -
143 y[ordering[levels[i] - 1]]));
144 stop = i < num_levels - 1 ? levels[i + 1] : n;
145 for (int j = levels[i]; j < stop; j++) {
146 y[ordering[j]] += displacement;
147 }
148 }
149 }
150 if (dim == 2) {
151 if (IMDS_given_dim(graph, n, y, x, Epsilon)) {
152 iterations = -1;
153 goto finish;
154 }
155 }
156 } else {
157 initLayout(n, dim, d_coords, nodes);
158 if (compute_hierarchy(graph, n, abs_tol, relative_tol, NULL, &ordering,
159 &levels, &num_levels)) {
160 iterations = -1;
161 goto finish;
162 }
163 }
164 if (n == 1) {
165 free(levels);
166 return 0;
167 }
168
169 /****************************************************
170 ** Compute the all-pairs-shortest-distances matrix **
171 ****************************************************/
172
173 if (maxi == 0) {
174 free(levels);
175 return iterations;
176 }
177
178 if (Verbose)
179 start_timer();
180
181 if (model == MODEL_SUBSET) {
182 /* weight graph to separate high-degree nodes */
183 /* and perform slower Dijkstra-based computation */
184 if (Verbose)
185 fprintf(stderr, "Calculating subset model");
187 } else if (model == MODEL_CIRCUIT) {
188 Dij = circuitModel(graph, n);
189 if (!Dij) {
191 "graph is disconnected. Hence, the circuit model\n");
193 "is undefined. Reverting to the shortest path model.\n");
194 }
195 } else if (model == MODEL_MDS) {
196 if (Verbose)
197 fprintf(stderr, "Calculating MDS model");
198 Dij = mdsModel(graph, n);
199 }
200 if (!Dij) {
201 if (Verbose)
202 fprintf(stderr, "Calculating shortest paths");
203 Dij = compute_apsp_packed(graph, n);
204 }
205 if (Verbose) {
206 fprintf(stderr, ": %.2f sec\n", elapsed_sec());
207 fprintf(stderr, "Setting initial positions");
208 start_timer();
209 }
210
211 diameter = -1;
212 length = n + n * (n - 1) / 2;
213 for (i = 0; i < length; i++) {
214 if (Dij[i] > diameter) {
215 diameter = (int) Dij[i];
216 }
217 }
218
219 if (!smart_ini) {
220 /* for numerical stability, scale down layout */
221 /* No Jiggling, might conflict with constraints */
222 double max = 1;
223 for (i = 0; i < dim; i++) {
224 for (int j = 0; j < n; j++) {
225 max = fmax(max, fabs(d_coords[i][j]));
226 }
227 }
228 for (i = 0; i < dim; i++) {
229 for (int j = 0; j < n; j++) {
230 d_coords[i][j] *= 10 / max;
231 }
232 }
233 }
234
235 if (levels_gap > 0) {
236 double sum1, sum2, scale_ratio;
237 int count;
238 sum1 = (float) (n * (n - 1) / 2);
239 sum2 = 0;
240 for (count = 0, i = 0; i < n - 1; i++) {
241 count++; // skip self distance
242 for (int j = i + 1; j < n; j++, count++) {
243 sum2 += distance_kD(d_coords, dim, i, j) / Dij[count];
244 }
245 }
246 scale_ratio = sum2 / sum1;
247 /* double scale_ratio=10; */
248 for (i = 0; i < length; i++) {
249 Dij[i] *= (float) scale_ratio;
250 }
251 }
252
253 /**************************
254 ** Layout initialization **
255 **************************/
256
257 for (i = 0; i < dim; i++) {
258 orthog1(n, d_coords[i]);
259 }
260
261 /* for the y-coords, don't center them, but translate them so y[0]=0 */
262 y_0 = d_coords[1][0];
263 for (i = 0; i < n; i++) {
264 d_coords[1][i] -= y_0;
265 }
266
267 coords = gv_calloc(dim, sizeof(float *));
268 f_storage = gv_calloc(dim * n, sizeof(float));
269 for (i = 0; i < dim; i++) {
270 coords[i] = f_storage + i * n;
271 for (int j = 0; j < n; j++) {
272 coords[i][j] = (float)d_coords[i][j];
273 }
274 }
275
276 /* compute constant term in stress sum
277 * which is \sum_{i<j} w_{ij}d_{ij}^2
278 */
279 constant_term = (float) (n * (n - 1) / 2);
280
281 if (Verbose)
282 fprintf(stderr, ": %.2f sec", elapsed_sec());
283
284 /**************************
285 ** Laplacian computation **
286 **************************/
287
288 lap2 = Dij;
289 lap_length = n + n * (n - 1) / 2;
290 square_vec(lap_length, lap2);
291 /* compute off-diagonal entries */
292 invert_vec(lap_length, lap2);
293
294 /* compute diagonal entries */
295 int count = 0;
296 degrees = gv_calloc(n, sizeof(double));
297 set_vector_val(n, 0, degrees);
298 for (i = 0; i < n - 1; i++) {
299 degree = 0;
300 count++; // skip main diag entry
301 for (int j = 1; j < n - i; j++, count++) {
302 val = lap2[count];
303 degree += val;
304 degrees[i + j] -= val;
305 }
306 degrees[i] -= degree;
307 }
308 for (step = n, count = 0, i = 0; i < n; i++, count += step, step--) {
309 lap2[count] = (float) degrees[i];
310 }
311
312 /*************************
313 ** Layout optimization **
314 *************************/
315
316 b = gv_calloc(dim, sizeof(float *));
317 b[0] = gv_calloc(dim * n, sizeof(float));
318 for (k = 1; k < dim; k++) {
319 b[k] = b[0] + k * n;
320 }
321
322 tmp_coords = gv_calloc(n, sizeof(float));
323 dist_accumulator = gv_calloc(n, sizeof(float));
324 lap1 = gv_calloc(lap_length, sizeof(float));
325
326 old_stress = DBL_MAX; /* at least one iteration */
327
328 cMajEnv =
329 initConstrainedMajorization(lap2, n, ordering, levels, num_levels);
330
331 for (converged = false, iterations = 0;
332 iterations < maxi && !converged; iterations++) {
333
334 /* First, construct Laplacian of 1/(d_ij*|p_i-p_j|) */
335 set_vector_val(n, 0, degrees);
336 sqrt_vecf(lap_length, lap2, lap1);
337 for (count = 0, i = 0; i < n - 1; i++) {
338 len = n - i - 1;
339 /* init 'dist_accumulator' with zeros */
340 set_vector_valf(n, 0, dist_accumulator);
341
342 /* put into 'dist_accumulator' all squared distances
343 * between 'i' and 'i'+1,...,'n'-1
344 */
345 for (k = 0; k < dim; k++) {
346 set_vector_valf(len, coords[k][i], tmp_coords);
347 vectors_mult_additionf(len, tmp_coords, -1, coords[k] + i + 1);
348 square_vec(len, tmp_coords);
349 vectors_additionf(len, tmp_coords, dist_accumulator, dist_accumulator);
350 }
351
352 /* convert to 1/d_{ij} */
353 invert_sqrt_vec(len, dist_accumulator);
354 /* detect overflows */
355 for (int j = 0; j < len; j++) {
356 if (dist_accumulator[j] >= FLT_MAX || dist_accumulator[j] < 0) {
357 dist_accumulator[j] = 0;
358 }
359 }
360
361 count++; /* save place for the main diagonal entry */
362 degree = 0;
363 for (int j = 0; j < len; j++, count++) {
364 val = lap1[count] *= dist_accumulator[j];
365 degree += val;
366 degrees[i + j + 1] -= val;
367 }
368 degrees[i] -= degree;
369 }
370 for (step = n, count = 0, i = 0; i < n; i++, count += step, step--) {
371 lap1[count] = (float) degrees[i];
372 }
373
374 /* Now compute b[] (L^(X(t))*X(t)) */
375 for (k = 0; k < dim; k++) {
376 /* b[k] := lap1*coords[k] */
377 right_mult_with_vector_ff(lap1, n, coords[k], b[k]);
378 }
379
380 /* compute new stress
381 * remember that the Laplacians are negated, so we subtract
382 * instead of add and vice versa
383 */
384 new_stress = 0;
385 for (k = 0; k < dim; k++) {
386 new_stress += vectors_inner_productf(n, coords[k], b[k]);
387 }
388 new_stress *= 2;
389 new_stress += constant_term; // only after mult by 2
390 for (k = 0; k < dim; k++) {
391 right_mult_with_vector_ff(lap2, n, coords[k], tmp_coords);
392 new_stress -= vectors_inner_productf(n, coords[k], tmp_coords);
393 }
394
395 /* check for convergence */
396 converged =
397 fabs(new_stress - old_stress) / fabs(old_stress + 1e-10) <
398 Epsilon;
399 converged |= iterations > 1 && new_stress > old_stress;
400 /* in first iteration we allowed stress increase, which
401 * might result ny imposing constraints
402 */
403 old_stress = new_stress;
404
405 for (k = 0; k < dim; k++) {
406 /* now we find the optimizer of trace(X'LX)+X'B by solving 'dim'
407 * system of equations, thereby obtaining the new coordinates.
408 * If we use the constraints (given by the var's: 'ordering',
409 * 'levels' and 'num_levels'), we cannot optimize
410 * trace(X'LX)+X'B by simply solving equations, but we have
411 * to use a quadratic programming solver
412 * note: 'lap2' is a packed symmetric matrix, that is its
413 * upper-triangular part is arranged in a vector row-wise
414 * also note: 'lap2' is really the negated laplacian (the
415 * laplacian is -'lap2')
416 */
417
418 if (k == 1) {
419 /* use quad solver in the y-dimension */
420 constrained_majorization_new_with_gaps(cMajEnv, b[k],
421 coords, k,
422 localConstrMajorIterations,
423 levels_gap);
424
425 } else {
426 /* use conjugate gradient for all dimensions except y */
427 if (conjugate_gradient_mkernel(lap2, coords[k], b[k], n,
428 conj_tol, n)) {
429 iterations = -1;
430 goto finish;
431 }
432 }
433 }
434 }
435
436 if (coords != NULL) {
437 for (i = 0; i < dim; i++) {
438 for (int j = 0; j < n; j++) {
439 d_coords[i][j] = coords[i][j];
440 }
441 }
442 free(coords[0]);
443 free(coords);
444 }
445
446 free(tmp_coords);
447 free(dist_accumulator);
448 free(degrees);
449 free(lap2);
450
451 free(lap1);
452
453finish:
454 if (cMajEnv != NULL) {
455 deleteCMajEnv(cMajEnv);
456 }
457 if (b) {
458 free(b[0]);
459 free(b);
460 }
461 free(ordering);
462
463 free(levels);
464
465 return iterations;
466}
467#endif /* DIGCOLA */
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:210
int conjugate_gradient_mkernel(float *A, float *x, float *b, int n, double tol, int max_iterations)
Definition conjgrad.c:160
static double len(glCompPoint p)
Definition glutils.c:150
static bool Verbose
Definition gml2gv.c:23
void free(void *)
node NULL
Definition grammar.y:163
void agwarningf(const char *fmt,...)
Definition agerror.c:173
int agerr(agerrlevel_t level, const char *fmt,...)
Definition agerror.c:155
@ AGPREV
Definition cgraph.h:849
Agraph_t * graph(char *name)
Definition gv.cpp:30
static opts_t opts
Definition gvgen.c:394
double distance_kD(double **coords, int dim, int i, int j)
Definition kkutils.c:113
void invert_vec(int n, float *vec)
Definition matrix_ops.c:509
void invert_sqrt_vec(int n, float *vec)
Definition matrix_ops.c:529
void vectors_additionf(int n, float *vector1, float *vector2, float *result)
Definition matrix_ops.c:442
void set_vector_valf(int n, float val, float *result)
Definition matrix_ops.c:484
void orthog1(int n, double *vec)
Definition matrix_ops.c:233
void sqrt_vecf(int n, float *source, float *target)
Definition matrix_ops.c:519
void set_vector_val(int n, double val, double *result)
Definition matrix_ops.c:477
void right_mult_with_vector_ff(float *packed_matrix, int n, float *vector, float *result)
Definition matrix_ops.c:409
void vectors_mult_additionf(int n, float *vector1, float alpha, float *vector2)
Definition matrix_ops.c:451
void square_vec(int n, float *vec)
Definition matrix_ops.c:501
double vectors_inner_productf(int n, float *vector1, float *vector2)
Definition matrix_ops.c:466
static const int dim
#define MODEL_SUBSET
Definition neato.h:17
#define MODEL_MDS
Definition neato.h:18
#define MODEL_CIRCUIT
Definition neato.h:16
int DistType
Definition sparsegraph.h:37
float * compute_apsp_artificial_weights_packed(vtx_data *graph, int n)
Definition stress.c:718
int stress_majorization_kD_mkernel(vtx_data *graph, int n, double **d_coords, node_t **nodes, int dim, int opts, int model, int maxi)
Definition stress.c:801
float * circuitModel(vtx_data *graph, int nG)
Definition stress.c:168
int initLayout(int n, int dim, double **coords, node_t **nodes)
Definition stress.c:130
float * mdsModel(vtx_data *graph, int nG)
Definition stress.c:667
float * compute_apsp_packed(vtx_data *graph, int n)
Definition stress.c:700
#define opt_smart_init
Definition stress.h:28
#define tolerance_cg
Definition stress.h:19
double elapsed_sec(void)
Definition timing.c:48
void start_timer(void)
Definition timing.c:43
#define MAX(a, b)
Definition write.c:31