Graphviz 12.0.1~dev.20240715.2254
Loading...
Searching...
No Matches
tred.c
Go to the documentation of this file.
1
12/*
13 * Copyright (c) 2011 AT&T Intellectual Property
14 * All rights reserved. This program and the accompanying materials
15 * are made available under the terms of the Eclipse Public License v1.0
16 * which accompanies this distribution, and is available at
17 * https://www.eclipse.org/legal/epl-v10.html
18 *
19 * Authors: Stephen North, Emden Gansner
20 * Contributors: Details at https://graphviz.org
21 */
22
23#include <cgraph/alloc.h>
24#include <cgraph/cghdr.h>
25#include <cgraph/stack.h>
26#include <stdbool.h>
27#include <stdio.h>
28#include <stdlib.h>
29#include <string.h>
30#include <time.h>
31
32typedef struct {
33 bool on_stack : 1;
34 unsigned char dist;
36
37#define ON_STACK(ninfo, n) (ninfo[AGSEQ(n)].on_stack)
38#define DISTANCE(ninfo, n) (ninfo[AGSEQ(n)].dist)
39#define agrootof(n) ((n)->root)
40
41static unsigned char uchar_min(unsigned char a, unsigned char b) {
42 if (a < b)
43 return a;
44 return b;
45}
46
47static void push(gv_stack_t *sp, Agedge_t *ep, nodeinfo_t *ninfo) {
48
49 // mark this edge on the stack
50 ON_STACK(ninfo, aghead(ep)) = true;
51
52 // insert the new edge
53 stack_push(sp, ep);
54}
55
56static Agedge_t *pop(gv_stack_t *sp, nodeinfo_t *ninfo) {
57
58 if (stack_is_empty(sp)) {
59 return NULL;
60 }
61
62 // remove the top
63 Agedge_t *e = stack_pop(sp);
64
65 // mark it as no longer on the stack
66 ON_STACK(ninfo, aghead(e)) = false;
67
68 return e;
69}
70
71static Agedge_t *top(gv_stack_t *sp) {
72
73 if (stack_is_empty(sp)) {
74 return NULL;
75 }
76
77 return stack_top(sp);
78}
79
80/* Main function for transitive reduction.
81 * This does a DFS starting at node n. Each node records the length of
82 * its largest simple path from n. We only care if the length is > 1. Node
83 * n will have distance 0; outneighbors of n will have distance 1 or 2; all
84 * others will have distance 2.
85 *
86 * During the DFS, we only push edges on the stack whose head has distance 0
87 * (i.e., hasn't been visited yet), setting its distance to the distance of the
88 * tail node plus one. If we find a head node with distance 1, we don't push the
89 * edge, since it has already been in a DFS, but we update its distance. We also
90 * check for back edges and report these.
91 *
92 * After the DFS, we check all outedges of n. Those edges whose head has
93 * distance 2 we delete. We also delete all but one copy of any edges with the
94 * same head.
95 */
96static int dfs(Agnode_t *n, nodeinfo_t *ninfo, int warn,
98 Agraph_t *g = agrootof(n);
99 Agedgepair_t dummy;
100 Agedge_t *link;
101 Agedge_t *next;
102 Agedge_t *prev;
103 Agedge_t *e;
104 Agedge_t *f;
105 Agnode_t *v;
106 Agnode_t *hd;
107 Agnode_t *oldhd;
108 int do_delete;
109
110 dummy.out.base.tag.objtype = AGOUTEDGE;
111 dummy.out.node = n;
112 dummy.in.base.tag.objtype = AGINEDGE;
113 dummy.in.node = NULL;
114
115 gv_stack_t estk = {0};
116 push(&estk, &dummy.out, ninfo);
117 prev = 0;
118
119 while ((link = top(&estk))) {
120 v = aghead(link);
121 if (prev)
122 next = agnxtout(g, prev);
123 else
124 next = agfstout(g, v);
125 for (; next; next = agnxtout(g, next)) {
126 hd = aghead(next);
127 if (hd == v)
128 continue; // Skip a loop
129 if (ON_STACK(ninfo, hd)) {
130 if (!warn) {
131 warn++;
132 if (opts->err != NULL) {
133 fprintf(
134 opts->err,
135 "warning: %s has cycle(s), transitive reduction not unique\n",
136 agnameof(g));
137 fprintf(opts->err, "cycle involves edge %s -> %s\n", agnameof(v),
138 agnameof(hd));
139 }
140 }
141 } else if (DISTANCE(ninfo, hd) == 0) {
142 DISTANCE(ninfo, hd) = uchar_min(1, DISTANCE(ninfo, v)) + 1;
143 break;
144 } else if (DISTANCE(ninfo, hd) == 1) {
145 DISTANCE(ninfo, hd) = uchar_min(1, DISTANCE(ninfo, v)) + 1;
146 }
147 }
148 if (next) {
149 push(&estk, next, ninfo);
150 prev = 0;
151 } else {
152 prev = pop(&estk, ninfo);
153 }
154 }
155 oldhd = NULL;
156 for (e = agfstout(g, n); e; e = f) {
157 do_delete = 0;
158 f = agnxtout(g, e);
159 hd = aghead(e);
160 if (oldhd == hd)
161 do_delete = 1;
162 else {
163 oldhd = hd;
164 if (DISTANCE(ninfo, hd) > 1)
165 do_delete = 1;
166 }
167 if (do_delete) {
168 if (opts->PrintRemovedEdges && opts->err != NULL)
169 fprintf(opts->err, "removed edge: %s: \"%s\" -> \"%s\"\n", agnameof(g),
170 agnameof(aghead(e)), agnameof(agtail(e)));
171 agdelete(g, e);
172 }
173 }
174 stack_reset(&estk);
175 return warn;
176}
177
178/* Do a DFS for each vertex in graph g, so the time
179 * complexity is O(|V||E|).
180 */
182 Agnode_t *n;
183 int cnt = 0;
184 int warn = 0;
185 time_t secs;
186 time_t total_secs = 0;
187 nodeinfo_t *ninfo;
188 size_t infosize;
189
190 infosize = (agnnodes(g) + 1) * sizeof(nodeinfo_t);
191 ninfo = gv_alloc(infosize);
192
193 if (opts->Verbose && opts->err != NULL)
194 fprintf(stderr, "Processing graph %s\n", agnameof(g));
195 for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
196 memset(ninfo, 0, infosize);
197 const time_t start = time(NULL);
198 warn = dfs(n, ninfo, warn, opts);
199 if (opts->Verbose) {
200 secs = time(NULL) - start;
201 total_secs += secs;
202 cnt++;
203 if (cnt % 1000 == 0 && opts->err != NULL) {
204 fprintf(opts->err, "[%d]\n", cnt);
205 }
206 }
207 }
208 if (opts->Verbose && opts->err != NULL)
209 fprintf(opts->err, "Finished graph %s: %lld.00 secs.\n", agnameof(g),
210 (long long)total_secs);
211 free(ninfo);
212 agwrite(g, opts->out);
213 fflush(opts->out);
214}
Memory allocation wrappers that exit on failure.
static void * gv_alloc(size_t size)
Definition alloc.h:47
cgraph.h additions
static Agnode_t * pop(void)
Definition ccomps.c:237
void free(void *)
node NULL
Definition grammar.y:149
static int cnt(Dict_t *d, Dtlink_t **set)
Definition graph.c:199
int agnnodes(Agraph_t *g)
Definition graph.c:158
void graphviz_tred(Agraph_t *g, const graphviz_tred_options_t *opts)
programmatic access to tred - transitive reduction
Definition tred.c:181
Agedge_t * agfstout(Agraph_t *g, Agnode_t *n)
Definition edge.c:23
#define agtail(e)
Definition cgraph.h:889
#define aghead(e)
Definition cgraph.h:890
Agedge_t * agnxtout(Agraph_t *g, Agedge_t *e)
Definition edge.c:38
int agwrite(Agraph_t *g, void *chan)
Return 0 on success, EOF on failure.
Definition write.c:708
Agnode_t * agnxtnode(Agraph_t *g, Agnode_t *n)
Definition node.c:47
Agnode_t * agfstnode(Agraph_t *g)
Definition node.c:40
char * agnameof(void *)
returns a string descriptor for the object.
Definition id.c:158
int agdelete(Agraph_t *g, void *obj)
deletes object. Equivalent to agclose, agdelnode, and agdeledge for obj being a graph,...
Definition obj.c:19
@ AGOUTEDGE
Definition cgraph.h:207
@ AGINEDGE
Definition cgraph.h:207
static opts_t opts
Definition gvgen.c:394
$2 u p prev
Definition htmlparse.y:495
static Agedge_t * top(gv_stack_t *sp)
Definition tred.c:71
static int dfs(Agnode_t *n, nodeinfo_t *ninfo, int warn, const graphviz_tred_options_t *opts)
Definition tred.c:96
static unsigned char uchar_min(unsigned char a, unsigned char b)
Definition tred.c:41
static void push(gv_stack_t *sp, Agedge_t *ep, nodeinfo_t *ninfo)
Definition tred.c:47
#define ON_STACK(ninfo, n)
Definition tred.c:37
#define agrootof(n)
Definition tred.c:39
#define DISTANCE(ninfo, n)
Definition tred.c:38
Implementation of a dynamically expanding stack data structure.
static void stack_push(gv_stack_t *stack, void *item)
Definition stack.h:21
static void * stack_pop(gv_stack_t *stack)
Definition stack.h:33
static void * stack_top(gv_stack_t *stack)
Definition stack.h:25
static void stack_reset(gv_stack_t *stack)
Definition stack.h:35
static bool stack_is_empty(const gv_stack_t *stack)
Definition stack.h:17
Agnode_t * node
Definition cgraph.h:272
Agobj_t base
Definition cgraph.h:269
Agedge_t in
Definition cgraph.h:276
Agedge_t out
Definition cgraph.h:276
Agtag_t tag
access with AGTAG
Definition cgraph.h:211
graph or subgraph
Definition cgraph.h:425
unsigned objtype
access with AGTYPE
Definition cgraph.h:199
options for passing to graphviz_tred
Definition cgraph.h:947
unsigned char dist
Definition tred.c:34
bool on_stack
Definition tred.c:33
int Verbose
Definition gvgen.c:43