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