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