Graphviz 13.0.0~dev.20241220.2304
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/cghdr.h>
24#include <cgraph/list.h>
25#include <stdbool.h>
26#include <stdio.h>
27#include <stdlib.h>
28#include <string.h>
29#include <time.h>
30#include <util/alloc.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
47DEFINE_LIST(edge_stack, Agedge_t *)
48
49static void push(edge_stack_t *sp, Agedge_t *ep, nodeinfo_t *ninfo) {
50
51 // mark this edge on the stack
52 ON_STACK(ninfo, aghead(ep)) = true;
53
54 // insert the new edge
55 edge_stack_push_back(sp, ep);
56}
57
58static Agedge_t *pop(edge_stack_t *sp, nodeinfo_t *ninfo) {
59
60 if (edge_stack_is_empty(sp)) {
61 return NULL;
62 }
63
64 // remove the top
65 Agedge_t *e = edge_stack_pop_back(sp);
66
67 // mark it as no longer on the stack
68 ON_STACK(ninfo, aghead(e)) = false;
69
70 return e;
71}
72
73static Agedge_t *top(edge_stack_t *sp) {
74
75 if (edge_stack_is_empty(sp)) {
76 return NULL;
77 }
78
79 return *edge_stack_back(sp);
80}
81
82/* Main function for transitive reduction.
83 * This does a DFS starting at node n. Each node records the length of
84 * its largest simple path from n. We only care if the length is > 1. Node
85 * n will have distance 0; outneighbors of n will have distance 1 or 2; all
86 * others will have distance 2.
87 *
88 * During the DFS, we only push edges on the stack whose head has distance 0
89 * (i.e., hasn't been visited yet), setting its distance to the distance of the
90 * tail node plus one. If we find a head node with distance 1, we don't push the
91 * edge, since it has already been in a DFS, but we update its distance. We also
92 * check for back edges and report these.
93 *
94 * After the DFS, we check all outedges of n. Those edges whose head has
95 * distance 2 we delete. We also delete all but one copy of any edges with the
96 * same head.
97 */
98static int dfs(Agnode_t *n, nodeinfo_t *ninfo, int warn,
100 Agraph_t *g = agrootof(n);
101 Agedgepair_t dummy;
102 Agedge_t *link;
103 Agedge_t *next;
104 Agedge_t *prev;
105 Agedge_t *e;
106 Agedge_t *f;
107 Agnode_t *v;
108 Agnode_t *hd;
109 Agnode_t *oldhd;
110 int do_delete;
111
112 dummy.out.base.tag.objtype = AGOUTEDGE;
113 dummy.out.node = n;
114 dummy.in.base.tag.objtype = AGINEDGE;
115 dummy.in.node = NULL;
116
117 edge_stack_t estk = {0};
118 push(&estk, &dummy.out, ninfo);
119 prev = 0;
120
121 while ((link = top(&estk))) {
122 v = aghead(link);
123 if (prev)
124 next = agnxtout(g, prev);
125 else
126 next = agfstout(g, v);
127 for (; next; next = agnxtout(g, next)) {
128 hd = aghead(next);
129 if (hd == v)
130 continue; // Skip a loop
131 if (ON_STACK(ninfo, hd)) {
132 if (!warn) {
133 warn++;
134 if (opts->err != NULL) {
135 fprintf(
136 opts->err,
137 "warning: %s has cycle(s), transitive reduction not unique\n",
138 agnameof(g));
139 fprintf(opts->err, "cycle involves edge %s -> %s\n", agnameof(v),
140 agnameof(hd));
141 }
142 }
143 } else if (DISTANCE(ninfo, hd) == 0) {
144 DISTANCE(ninfo, hd) = uchar_min(1, DISTANCE(ninfo, v)) + 1;
145 break;
146 } else if (DISTANCE(ninfo, hd) == 1) {
147 DISTANCE(ninfo, hd) = uchar_min(1, DISTANCE(ninfo, v)) + 1;
148 }
149 }
150 if (next) {
151 push(&estk, next, ninfo);
152 prev = 0;
153 } else {
154 prev = pop(&estk, ninfo);
155 }
156 }
157 oldhd = NULL;
158 for (e = agfstout(g, n); e; e = f) {
159 do_delete = 0;
160 f = agnxtout(g, e);
161 hd = aghead(e);
162 if (oldhd == hd)
163 do_delete = 1;
164 else {
165 oldhd = hd;
166 if (DISTANCE(ninfo, hd) > 1)
167 do_delete = 1;
168 }
169 if (do_delete) {
170 if (opts->PrintRemovedEdges && opts->err != NULL)
171 fprintf(opts->err, "removed edge: %s: \"%s\" -> \"%s\"\n", agnameof(g),
172 agnameof(aghead(e)), agnameof(agtail(e)));
173 agdelete(g, e);
174 }
175 }
176 edge_stack_free(&estk);
177 return warn;
178}
179
180/* Do a DFS for each vertex in graph g, so the time
181 * complexity is O(|V||E|).
182 */
184 Agnode_t *n;
185 int cnt = 0;
186 int warn = 0;
187 time_t secs;
188 time_t total_secs = 0;
189 nodeinfo_t *ninfo;
190 size_t infosize;
191
192 infosize = (agnnodes(g) + 1) * sizeof(nodeinfo_t);
193 ninfo = gv_alloc(infosize);
194
195 if (opts->Verbose && opts->err != NULL)
196 fprintf(stderr, "Processing graph %s\n", agnameof(g));
197 for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
198 memset(ninfo, 0, infosize);
199 const time_t start = time(NULL);
200 warn = dfs(n, ninfo, warn, opts);
201 if (opts->Verbose) {
202 secs = time(NULL) - start;
203 total_secs += secs;
204 cnt++;
205 if (cnt % 1000 == 0 && opts->err != NULL) {
206 fprintf(opts->err, "[%d]\n", cnt);
207 }
208 }
209 }
210 if (opts->Verbose && opts->err != NULL)
211 fprintf(opts->err, "Finished graph %s: %lld.00 secs.\n", agnameof(g),
212 (long long)total_secs);
213 free(ninfo);
214 agwrite(g, opts->out);
215 fflush(opts->out);
216}
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:231
void free(void *)
node NULL
Definition grammar.y:163
static int cnt(Dict_t *d, Dtlink_t **set)
Definition graph.c:210
int agnnodes(Agraph_t *g)
Definition graph.c:169
void graphviz_tred(Agraph_t *g, const graphviz_tred_options_t *opts)
programmatic access to tred - transitive reduction
Definition tred.c:183
Agedge_t * agfstout(Agraph_t *g, Agnode_t *n)
Definition edge.c:24
#define agtail(e)
Definition cgraph.h:880
#define aghead(e)
Definition cgraph.h:881
Agedge_t * agnxtout(Agraph_t *g, Agedge_t *e)
Definition edge.c:39
int agwrite(Agraph_t *g, void *chan)
Return 0 on success, EOF on failure.
Definition write.c:730
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:20
@ 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:297
static int dfs(Agnode_t *n, nodeinfo_t *ninfo, int warn, const graphviz_tred_options_t *opts)
Definition tred.c:98
static unsigned char uchar_min(unsigned char a, unsigned char b)
Definition tred.c:41
static void push(edge_stack_t *sp, Agedge_t *ep, nodeinfo_t *ninfo)
Definition tred.c:49
#define ON_STACK(ninfo, n)
Definition tred.c:37
#define agrootof(n)
Definition tred.c:39
#define DISTANCE(ninfo, n)
Definition tred.c:38
static Agedge_t * top(edge_stack_t *sp)
Definition tred.c:73
#define DEFINE_LIST(name, type)
Definition list.h:26
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:938
unsigned char dist
Definition tred.c:34
bool on_stack
Definition tred.c:33
int Verbose
Definition gvgen.c:43