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