Graphviz 15.1.0~dev.20260613.0511
Loading...
Searching...
No Matches
quad_prog_vpsc.c
Go to the documentation of this file.
1
17/**********************************************************
18 *
19 * Solve a quadratic function f(X) = X' e->A X + b X
20 * subject to a set of separation constraints e->cs
21 *
22 * Tim Dwyer, 2006
23 **********************************************************/
24
25#include "config.h"
26
27#include <common/geomprocs.h>
28#include <neatogen/digcola.h>
29#include <stdbool.h>
30#include <util/alloc.h>
31#include <util/gv_math.h>
32#ifdef IPSEPCOLA
33#include <math.h>
34#include <stdlib.h>
35#include <time.h>
36#include <stdio.h>
37#include <float.h>
38#include <assert.h>
39#include <neatogen/matrix_ops.h>
40#include <neatogen/kkutils.h>
41#include <vpsc/csolve_VPSC.h>
44
45/* #define CONMAJ_LOGGING 1 */
46#define quad_prog_tol 1e-4
47
48/*
49 * Use gradient-projection to solve Variable Placement with Separation Constraints problem.
50 */
51int
52constrained_majorization_vpsc(CMajEnvVPSC * e, float *b, float *place,
53 int max_iterations)
54{
55 int i, j, counter;
56 float *g, *old_place, *d;
57 /* for laplacian computation need number of real vars and those
58 * dummy vars included in lap
59 */
60 int n = e->nv + e->nldv;
61 bool converged = false;
62#ifdef CONMAJ_LOGGING
63 static int call_no = 0;
64#endif /* CONMAJ_LOGGING */
65
66 if (max_iterations == 0)
67 return 0;
68 g = e->fArray1;
69 old_place = e->fArray2;
70 d = e->fArray3;
71 if (e->m > 0) {
72 for (i = 0; i < n; i++) {
73 setVariableDesiredPos(e->vs[i], place[i]);
74 }
75 satisfyVPSC(e->vpsc);
76 for (i = 0; i < n; i++) {
77 place[i] = getVariablePos(e->vs[i]);
78 }
79 }
80#ifdef CONMAJ_LOGGING
81 float prev_stress = 0;
82 for (i = 0; i < n; i++) {
83 prev_stress += 2 * b[i] * place[i];
84 for (j = 0; j < n; j++) {
85 prev_stress -= e->A[i][j] * place[j] * place[i];
86 }
87 }
88 FILE *logfile = fopen("constrained_majorization_log", "a");
89#endif
90
91 for (counter = 0; counter < max_iterations && !converged; counter++) {
92 float test = 0;
93 float alpha, beta;
94 float numerator = 0, denominator = 0, r;
95 converged = true;
96 /* find steepest descent direction */
97 for (i = 0; i < n; i++) {
98 old_place[i] = place[i];
99 g[i] = 2 * b[i];
100 for (j = 0; j < n; j++) {
101 g[i] -= 2 * e->A[i][j] * place[j];
102 }
103 }
104 for (i = 0; i < n; i++) {
105 numerator += g[i] * g[i];
106 r = 0;
107 for (j = 0; j < n; j++) {
108 r += 2 * e->A[i][j] * g[j];
109 }
110 denominator -= r * g[i];
111 }
112 if (denominator != 0)
113 alpha = numerator / denominator;
114 else
115 alpha = 1.0;
116 for (i = 0; i < n; i++) {
117 place[i] -= alpha * g[i];
118 }
119 if (e->m > 0) {
120 /* project to constraint boundary */
121 for (i = 0; i < n; i++) {
122 setVariableDesiredPos(e->vs[i], place[i]);
123 }
124 satisfyVPSC(e->vpsc);
125 for (i = 0; i < n; i++) {
126 place[i] = getVariablePos(e->vs[i]);
127 }
128 }
129 /* set place to the intersection of old_place-g and boundary and
130 * compute d, the vector from intersection pnt to projection pnt
131 */
132 for (i = 0; i < n; i++) {
133 d[i] = place[i] - old_place[i];
134 }
135 /* now compute beta */
136 numerator = 0, denominator = 0;
137 for (i = 0; i < n; i++) {
138 numerator += g[i] * d[i];
139 r = 0;
140 for (j = 0; j < n; j++) {
141 r += 2 * e->A[i][j] * d[j];
142 }
143 denominator += r * d[i];
144 }
145 if (!is_exactly_zero(denominator) && !is_exactly_equal(denominator, -0.0))
146 beta = numerator / denominator;
147 else
148 beta = 1.0;
149
150 for (i = 0; i < n; i++) {
151 /* beta > 1.0 takes us back outside the feasible region
152 * beta < 0 clearly not useful and may happen due to numerical imp.
153 */
154 if (beta > 0 && beta < 1.0) {
155 place[i] = old_place[i] + beta * d[i];
156 }
157 test += fabsf(place[i] - old_place[i]);
158 }
159#ifdef CONMAJ_LOGGING
160 float stress = 0;
161 for (i = 0; i < n; i++) {
162 stress += 2 * b[i] * place[i];
163 for (j = 0; j < n; j++) {
164 stress -= e->A[i][j] * place[j] * place[i];
165 }
166 }
167 fprintf(logfile, "%d: stress=%f, test=%f, %s\n", call_no, stress,
168 test, (stress >= prev_stress) ? "No Improvement" : "");
169 prev_stress = stress;
170#endif
171 if (test > quad_prog_tol) {
172 converged = false;
173 }
174 }
175#ifdef CONMAJ_LOGGING
176 call_no++;
177 fclose(logfile);
178#endif
179 return counter;
180}
181
182static DigColaLevel *assign_digcola_levels(const int *ordering, int n,
183 int *level_inds, int num_divisions);
184
185/*
186 * Set up environment and global constraints (dir-edge constraints, containment constraints
187 * etc).
188 *
189 * diredges: 0=no dir edge constraints
190 * 1=one separation constraint for each edge (in acyclic subgraph)
191 * 2=DiG-CoLa level constraints
192 */
193CMajEnvVPSC *initCMajVPSC(int n, float *packedMat, vtx_data * graph,
194 ipsep_options * opt, int diredges)
195{
196 int i;
197 /* nv is the number of real nodes */
198 int nConCs;
199 CMajEnvVPSC *e = gv_alloc(sizeof(CMajEnvVPSC));
200 e->A = NULL;
201 /* if we have clusters then we'll need two constraints for each var in
202 * a cluster */
203 e->nldv = 2 * opt->clusters.nclusters;
204 e->nv = n - e->nldv;
205 e->ndv = 0;
206
207 e->gcs = NULL;
208 e->vs = gv_calloc(n, sizeof(Variable*));
209 for (i = 0; i < n; i++) {
210 e->vs[i] = newVariable(i, 1.0, 1.0);
211 }
212 e->gm = 0;
213 if (diredges == 1) {
214 if (Verbose)
215 fprintf(stderr, " generate edge constraints...\n");
216 for (i = 0; i < e->nv; i++) {
217 for (size_t j = 1; j < graph[i].nedges; j++) {
218 if (graph[i].edists[j] > 0) {
219 e->gm++;
220 }
221 }
222 }
223 e->gcs = newConstraints(e->gm);
224 e->gm = 0;
225 for (i = 0; i < e->nv; i++) {
226 for (size_t j = 1; j < graph[i].nedges; j++) {
227 int u = i, v = graph[i].edges[j];
228 if (graph[i].edists[j] > 0) {
229 e->gcs[e->gm++] =
230 newConstraint(e->vs[u], e->vs[v], opt->edge_gap);
231 }
232 }
233 }
234 } else if (diredges == 2) {
235 int *ordering = NULL, *ls = NULL, cvar;
236 double halfgap;
237 DigColaLevel *levels;
238 Variable **vs = e->vs;
239 /* e->ndv is the number of dummy variables required, one for each boundary */
240 if (compute_hierarchy(graph, e->nv, 1e-2, 1e-1, NULL, &ordering, &ls,
241 &e->ndv)) return NULL;
242 levels = assign_digcola_levels(ordering, e->nv, ls, e->ndv);
243 free(ordering);
244 if (Verbose)
245 fprintf(stderr, "Found %d DiG-CoLa boundaries\n", e->ndv);
246 e->gm =
247 get_num_digcola_constraints(levels, e->ndv + 1) + e->ndv - 1;
248 e->gcs = newConstraints(e->gm);
249 e->gm = 0;
250 e->vs = gv_calloc(n + e->ndv, sizeof(Variable*));
251 for (i = 0; i < n; i++) {
252 e->vs[i] = vs[i];
253 }
254 free(vs);
255 /* create dummy vars */
256 for (i = 0; i < e->ndv; i++) {
257 /* dummy vars should have 0 weight */
258 cvar = n + i;
259 e->vs[cvar] = newVariable(cvar, 1.0, 0.000001);
260 }
261 halfgap = opt->edge_gap;
262 for (i = 0; i < e->ndv; i++) {
263 cvar = n + i;
264 /* outgoing constraints for each var in level below boundary */
265 for (int j = 0; j < levels[i].num_nodes; j++) {
266 e->gcs[e->gm++] =
267 newConstraint(e->vs[levels[i].nodes[j]], e->vs[cvar],
268 halfgap);
269 }
270 /* incoming constraints for each var in level above boundary */
271 for (int j = 0; j < levels[i + 1].num_nodes; j++) {
272 e->gcs[e->gm++] =
273 newConstraint(e->vs[cvar],
274 e->vs[levels[i + 1].nodes[j]], halfgap);
275 }
276 }
277 /* constraints between adjacent boundary dummy vars */
278 for (i = 0; i < e->ndv - 1; i++) {
279 e->gcs[e->gm++] =
280 newConstraint(e->vs[n + i], e->vs[n + i + 1], 0);
281 }
282 }
283 if (opt->clusters.nclusters > 0) {
284 Constraint **ecs = e->gcs;
285 nConCs = 2 * opt->clusters.nvars;
286 e->gcs = newConstraints(e->gm + nConCs);
287 for (i = 0; i < e->gm; i++) {
288 e->gcs[i] = ecs[i];
289 }
290 if (ecs != NULL)
291 deleteConstraints(0, ecs);
292 for (i = 0; i < opt->clusters.nclusters; i++) {
293 for (int j = 0; j < opt->clusters.clustersizes[i]; j++) {
294 Variable *v = e->vs[opt->clusters.clusters[i][j]];
295 Variable *cl = e->vs[e->nv + 2 * i];
296 Variable *cr = e->vs[e->nv + 2 * i + 1];
297 e->gcs[e->gm++] = newConstraint(cl, v, 0);
298 e->gcs[e->gm++] = newConstraint(v, cr, 0);
299 }
300 }
301 }
302
303 e->m = 0;
304 e->cs = NULL;
305 if (e->gm > 0) {
306 e->vpsc = newIncVPSC(n + e->ndv, e->vs, e->gm, e->gcs);
307 e->m = e->gm;
308 e->cs = e->gcs;
309 }
310 if (packedMat != NULL) {
311 e->A = unpackMatrix(packedMat, n);
312 }
313
314 e->fArray1 = gv_calloc(n, sizeof(float));
315 e->fArray2 = gv_calloc(n, sizeof(float));
316 e->fArray3 = gv_calloc(n, sizeof(float));
317 if (Verbose)
318 fprintf(stderr,
319 " initCMajVPSC done: %d global constraints generated.\n",
320 e->m);
321 return e;
322}
323
324void deleteCMajEnvVPSC(CMajEnvVPSC * e)
325{
326 int i;
327 if (e->A != NULL) {
328 free(e->A[0]);
329 free(e->A);
330 }
331 if (e->m > 0) {
332 deleteVPSC(e->vpsc);
333 if (e->cs != e->gcs && e->gcs != NULL)
334 deleteConstraints(0, e->gcs);
335 deleteConstraints(e->m, e->cs);
336 for (i = 0; i < e->nv + e->nldv + e->ndv; i++) {
337 deleteVariable(e->vs[i]);
338 }
339 free(e->vs);
340 }
341 free(e->fArray1);
342 free(e->fArray2);
343 free(e->fArray3);
344 free(e);
345}
346
347/* generate non-overlap constraints inside each cluster, including dummy
348 * nodes at bounds of cluster
349 * generate constraints again for top level nodes and clusters treating
350 * clusters as rectangles of dim (l,r,b,t)
351 * for each cluster map in-constraints to l out-constraints to r
352 *
353 * For now, we'll keep the global containment constraints already
354 * generated for each cluster, and simply generate non-overlap constraints
355 * for all nodes and then an additional set of non-overlap constraints for
356 * clusters that we'll map back to the dummy vars as above.
357 */
358void generateNonoverlapConstraints(CMajEnvVPSC * e,
359 float nsizeScale,
360 float **coords,
361 int k,
362 bool transitiveClosure,
363 ipsep_options * opt)
364{
365 Constraint **csol, **csolptr;
366 int i, j, mol = 0;
367 int n = e->nv + e->nldv;
368 boxf* bb = gv_calloc(n, sizeof(boxf));
369 bool genclusters = opt->clusters.nclusters > 0;
370 if (genclusters) {
371 /* n is the number of real variables, not dummy cluster vars */
372 n -= 2 * opt->clusters.nclusters;
373 }
374 if (k == 0) {
375 /* grow a bit in the x dimension, so that if overlap is resolved
376 * horizontally then it won't be considered overlapping vertically
377 */
378 nsizeScale *= 1.0001f;
379 }
380 for (i = 0; i < n; i++) {
381 bb[i].LL.x =
382 coords[0][i] - nsizeScale * opt->nsize[i].x / 2.0 -
383 opt->gap.x / 2.0;
384 bb[i].UR.x =
385 coords[0][i] + nsizeScale * opt->nsize[i].x / 2.0 +
386 opt->gap.x / 2.0;
387 bb[i].LL.y =
388 coords[1][i] - nsizeScale * opt->nsize[i].y / 2.0 -
389 opt->gap.y / 2.0;
390 bb[i].UR.y =
391 coords[1][i] + nsizeScale * opt->nsize[i].y / 2.0 +
392 opt->gap.y / 2.0;
393 }
394 if (genclusters) {
395 Constraint ***cscl = gv_calloc(opt->clusters.nclusters + 1,
396 sizeof(Constraint**));
397 int* cm = gv_calloc(opt->clusters.nclusters + 1, sizeof(int));
398 for (i = 0; i < opt->clusters.nclusters; i++) {
399 int cn = opt->clusters.clustersizes[i];
400 Variable** cvs = gv_calloc(cn + 2, sizeof(Variable*));
401 boxf* cbb = gv_calloc(cn + 2, sizeof(boxf));
402 /* compute cluster bounding bb */
403 boxf container;
404 container.LL.x = container.LL.y = DBL_MAX;
405 container.UR.x = container.UR.y = -DBL_MAX;
406 for (j = 0; j < cn; j++) {
407 int iv = opt->clusters.clusters[i][j];
408 cvs[j] = e->vs[iv];
409 B2BF(bb[iv], cbb[j]);
410 EXPANDBB(&container, bb[iv]);
411 }
412 B2BF(container, opt->clusters.bb[i]);
413 cvs[cn] = e->vs[n + 2 * i];
414 cvs[cn + 1] = e->vs[n + 2 * i + 1];
415 B2BF(container, cbb[cn]);
416 B2BF(container, cbb[cn + 1]);
417 if (k == 0) {
418 cbb[cn].UR.x = container.LL.x + 0.0001;
419 cbb[cn + 1].LL.x = container.UR.x - 0.0001;
420 cm[i] =
421 genXConstraints(cn + 2, cbb, cvs, &cscl[i],
422 transitiveClosure);
423 } else {
424 cbb[cn].UR.y = container.LL.y + 0.0001;
425 cbb[cn + 1].LL.y = container.UR.y - 0.0001;
426 cm[i] = genYConstraints(cn + 2, cbb, cvs, &cscl[i]);
427 }
428 mol += cm[i];
429 free (cvs);
430 free (cbb);
431 }
432 /* generate top level constraints */
433 {
434 int cn = opt->clusters.ntoplevel + opt->clusters.nclusters;
435 Variable** cvs = gv_calloc(cn, sizeof(Variable*));
436 boxf* cbb = gv_calloc(cn, sizeof(boxf));
437 for (i = 0; i < opt->clusters.ntoplevel; i++) {
438 int iv = opt->clusters.toplevel[i];
439 cvs[i] = e->vs[iv];
440 B2BF(bb[iv], cbb[i]);
441 }
442 /* make dummy variables for clusters */
443 for (i = opt->clusters.ntoplevel; i < cn; i++) {
444 cvs[i] = newVariable(123 + i, 1, 1);
445 j = i - opt->clusters.ntoplevel;
446 B2BF(opt->clusters.bb[j], cbb[i]);
447 }
448 i = opt->clusters.nclusters;
449 if (k == 0) {
450 cm[i] =
451 genXConstraints(cn, cbb, cvs, &cscl[i],
452 transitiveClosure);
453 } else {
454 cm[i] = genYConstraints(cn, cbb, cvs, &cscl[i]);
455 }
456 /* remap constraints from tmp dummy vars to cluster l and r vars */
457 for (i = opt->clusters.ntoplevel; i < cn; i++) {
458 double dgap;
459 j = i - opt->clusters.ntoplevel;
460 /* dgap is the change in required constraint gap.
461 * since we are going from a source rectangle the size
462 * of the cluster bounding box to a zero width (in x dim,
463 * zero height in y dim) rectangle, the change will be
464 * half the bb width.
465 */
466 if (k == 0) {
467 dgap = -(cbb[i].UR.x - cbb[i].LL.x) / 2.0;
468 } else {
469 dgap = -(cbb[i].UR.y - cbb[i].LL.y) / 2.0;
470 }
471 remapInConstraints(cvs[i], e->vs[n + 2 * j], dgap);
472 remapOutConstraints(cvs[i], e->vs[n + 2 * j + 1], dgap);
473 /* there may be problems with cycles between
474 * cluster non-overlap and diredge constraints,
475 * to resolve:
476 *
477 * for each constraint c:v->cvs[i]:
478 * if exists diredge constraint u->v where u in c:
479 * remap v->cl to cr->v (gap = height(v)/2)
480 *
481 * in = getInConstraints(cvs[i])
482 * for(c : in) {
483 * assert(c.right==cvs[i]);
484 * vin = getOutConstraints(v=c.left)
485 * for(d : vin) {
486 * if(d.left.cluster==i):
487 * tmp = d.left
488 * d.left = d.right
489 * d.right = tmp
490 * d.gap = height(d.right)/2
491 * }
492 * }
493 *
494 */
495 deleteVariable(cvs[i]);
496 }
497 mol += cm[opt->clusters.nclusters];
498 free (cvs);
499 free (cbb);
500 }
501 csolptr = csol = newConstraints(mol);
502 for (i = 0; i < opt->clusters.nclusters + 1; i++) {
503 /* copy constraints into csol */
504 for (j = 0; j < cm[i]; j++) {
505 *csolptr++ = cscl[i][j];
506 }
507 deleteConstraints(0, cscl[i]);
508 }
509 free (cscl);
510 free (cm);
511 } else {
512 if (k == 0) {
513 mol = genXConstraints(n, bb, e->vs, &csol, transitiveClosure);
514 } else {
515 mol = genYConstraints(n, bb, e->vs, &csol);
516 }
517 }
518 /* remove constraints from previous iteration */
519 if (e->m > 0) {
520 /* can't reuse instance of VPSC when constraints change! */
521 deleteVPSC(e->vpsc);
522 for (i = e->gm; i < e->m; i++) {
523 /* delete previous overlap constraints */
524 deleteConstraint(e->cs[i]);
525 }
526 /* just delete the array, not the elements */
527 if (e->cs != e->gcs)
528 deleteConstraints(0, e->cs);
529 }
530 /* if we have no global constraints then the overlap constraints
531 * are all we have to worry about.
532 * Otherwise, we have to copy the global and overlap constraints
533 * into the one array
534 */
535 if (e->gm == 0) {
536 e->m = mol;
537 e->cs = csol;
538 } else {
539 e->m = mol + e->gm;
540 e->cs = newConstraints(e->m);
541 for (i = 0; i < e->m; i++) {
542 if (i < e->gm) {
543 e->cs[i] = e->gcs[i];
544 } else {
545 e->cs[i] = csol[i - e->gm];
546 }
547 }
548 /* just delete the array, not the elements */
549 deleteConstraints(0, csol);
550 }
551 if (Verbose)
552 fprintf(stderr, " generated %d constraints\n", e->m);
553 e->vpsc = newIncVPSC(e->nv + e->nldv + e->ndv, e->vs, e->m, e->cs);
554 free (bb);
555}
556
557/*
558 * Statically remove overlaps, that is remove all overlaps by moving each node as
559 * little as possible.
560 */
561void removeoverlaps(int n, float **coords, ipsep_options * opt)
562{
563 int i;
564 CMajEnvVPSC *e = initCMajVPSC(n, NULL, NULL, opt, 0);
565 generateNonoverlapConstraints(e, 1.0, coords, 0, true, opt);
566 solveVPSC(e->vpsc);
567 for (i = 0; i < n; i++) {
568 coords[0][i] = getVariablePos(e->vs[i]);
569 }
570 generateNonoverlapConstraints(e, 1.0, coords, 1, false, opt);
571 solveVPSC(e->vpsc);
572 for (i = 0; i < n; i++) {
573 coords[1][i] = getVariablePos(e->vs[i]);
574 }
575 deleteCMajEnvVPSC(e);
576}
577
578/*
579 unpack the "ordering" array into an array of DigColaLevel
580*/
581static DigColaLevel *assign_digcola_levels(const int *ordering, int n,
582 int *level_inds, int num_divisions) {
583 int i, j;
584 DigColaLevel *l = gv_calloc(num_divisions + 1, sizeof(DigColaLevel));
585 /* first level */
586 l[0].num_nodes = level_inds[0];
587 l[0].nodes = gv_calloc(l[0].num_nodes, sizeof(int));
588 for (i = 0; i < l[0].num_nodes; i++) {
589 l[0].nodes[i] = ordering[i];
590 }
591 /* second through second last level */
592 for (i = 1; i < num_divisions; i++) {
593 l[i].num_nodes = level_inds[i] - level_inds[i - 1];
594 l[i].nodes = gv_calloc(l[i].num_nodes, sizeof(int));
595 for (j = 0; j < l[i].num_nodes; j++) {
596 l[i].nodes[j] = ordering[level_inds[i - 1] + j];
597 }
598 }
599 /* last level */
600 if (num_divisions > 0) {
601 l[num_divisions].num_nodes = n - level_inds[num_divisions - 1];
602 l[num_divisions].nodes = gv_calloc(l[num_divisions].num_nodes, sizeof(int));
603 for (i = 0; i < l[num_divisions].num_nodes; i++) {
604 l[num_divisions].nodes[i] =
605 ordering[level_inds[num_divisions - 1] + i];
606 }
607 }
608 return l;
609}
610
611/*********************
612get number of separation constraints based on the number of nodes in each level
613ie, num_sep_constraints = sum_i^{num_levels-1} (|L[i]|+|L[i+1]|)
614**********************/
615int get_num_digcola_constraints(DigColaLevel * levels, int num_levels)
616{
617 int i, nc = 0;
618 for (i = 1; i < num_levels; i++) {
619 nc += levels[i].num_nodes + levels[i - 1].num_nodes;
620 }
621 nc += levels[0].num_nodes + levels[num_levels - 1].num_nodes;
622 return nc;
623}
624
625#endif /* IPSEPCOLA */
Memory allocation wrappers that exit on failure.
static void * gv_calloc(size_t nmemb, size_t size)
Definition alloc.h:26
static void * gv_alloc(size_t size)
Definition alloc.h:47
void setVariableDesiredPos(Variable *v, double desiredPos)
Variable * newVariable(int id, double desiredPos, double weight)
Bridge for C programs to access solve_VPSC (which is in C++)
Constraint ** newConstraints(int m)
void deleteConstraint(Constraint *c)
VPSC * newIncVPSC(int n, Variable *vs[], int m, Constraint *cs[])
void deleteVariable(Variable *v)
void deleteConstraints(int m, Constraint **cs)
Constraint * newConstraint(Variable *left, Variable *right, double gap)
void remapInConstraints(Variable *u, Variable *v, double dgap)
int genXConstraints(int n, boxf *bb, Variable **vs, Constraint ***cs, bool transitiveClosure)
void remapOutConstraints(Variable *u, Variable *v, double dgap)
double getVariablePos(const Variable *v)
void satisfyVPSC(VPSC *vpsc)
int genYConstraints(int n, boxf *bb, Variable **vs, Constraint ***cs)
void deleteVPSC(VPSC *vpsc)
void solveVPSC(VPSC *vpsc)
#define B2BF(b, bf)
Definition geom.h:69
geometric functions (e.g. on points and boxes)
#define EXPANDBB(b0, b1)
Definition geomprocs.h:65
static bool Verbose
Definition gml2gv.c:26
void free(void *)
node NULL
Definition grammar.y:181
Agraph_t * graph(char *name)
Definition gv.cpp:34
Arithmetic helper functions.
static bool is_exactly_zero(double v)
is a value precisely 0.0?
Definition gv_math.h:67
static bool is_exactly_equal(double a, double b)
are two values precisely the same?
Definition gv_math.h:48
#define alpha
Definition shapes.c:4034
A constraint determines a minimum or exact spacing required between two variables.
Definition constraint.h:25
Definition geom.h:41
pointf UR
Definition geom.h:41
pointf LL
Definition geom.h:41
double x
Definition geom.h:29
double y
Definition geom.h:29