bend_visgraph.bend
bend-visgraph@2.0.0.0 · 283 lines · 41 definitions
1
2
3
4
5
6
7import Base
8import bend-visgraph@2.0.0.0/visgraph.bend as VG
9import bend-visgraph@2.0.0.0/parallel.bend as Par
10import bend-visgraph@2.0.0.0/src/series.bend as Series
11import bend-visgraph@2.0.0.0/src/report.bend as Report
12
13type Mode is Data:
14 EdgeList{}
15 DegreeSequence{}
16 DegreeDistribution{}
17 DegreeCounts{}
18
19type Flag is Data:
20 Output{}
21 XFile{}
22 GraphType{}
23 Directed{}
24 Weighted{}
25 Limit{}
26 OutputMode{}
27
28type Entry<-A: Data> is Data:
29 Entry{name: String, value: A}
30
31type Config is Data:
32 Config{output: String, xs: String, g: VG.Visibility, dir: VG.Direction, k: VG.Weight, limit: U32, mode: Mode}
33 Usage{msg: String}
34
35type Outcome is Data:
36 Ok{text: String}
37 Err{msg: String}
38
39def usage() -> String:
40 "usage: bend_visgraph INPUT [-o OUTPUT] [-x XS] [-t natural|horizontal] [-d left_to_right|top_to_bottom] [-w WEIGHT] [-p LIMIT] [-m el|ds|dd|dc]"
41
42
43
44def flags() -> List<&2, Entry<Flag>>:
45 [Entry{"-o", Output{}}, Entry{"--output", Output{}}, Entry{"-x", XFile{}}, Entry{"--xs", XFile{}}, Entry{"-t", GraphType{}}, Entry{"--type", GraphType{}},
46 Entry{"-d", Directed{}}, Entry{"--directed", Directed{}}, Entry{"-w", Weighted{}}, Entry{"--weighted", Weighted{}},
47 Entry{"-p", Limit{}}, Entry{"--penetrable_limit", Limit{}}, Entry{"-m", OutputMode{}}, Entry{"--outputmode", OutputMode{}}]
48
49def graphs() -> List<&2, Entry<VG.Visibility>>:
50 [Entry{"natural", VG.Natural{}}, Entry{"horizontal", VG.Horizontal{}}]
51
52def directions() -> List<&2, Entry<VG.Direction>>:
53 [Entry{"left_to_right", VG.LeftToRight{}}, Entry{"top_to_bottom", VG.TopToBottom{}}]
54
55def weights() -> List<&2, Entry<VG.Weight>>:
56 [Entry{"distance", VG.Distance{}}, Entry{"sq_distance", VG.SqDistance{}}, Entry{"v_distance", VG.VDistance{}},
57 Entry{"abs_v_distance", VG.AbsVDistance{}}, Entry{"h_distance", VG.HDistance{}}, Entry{"abs_h_distance", VG.AbsHDistance{}},
58 Entry{"slope", VG.Slope{}}, Entry{"abs_slope", VG.AbsSlope{}}, Entry{"angle", VG.Angle{}}, Entry{"abs_angle", VG.AbsAngle{}},
59 Entry{"num_penetrations", VG.NumPenetrations{}}]
60
61def modes() -> List<&2, Entry<Mode>>:
62 [Entry{"el", EdgeList{}}, Entry{"ds", DegreeSequence{}}, Entry{"dd", DegreeDistribution{}}, Entry{"dc", DegreeCounts{}}]
63
64def lookup.hit(-A: Data, hit: Bool, v: A, rest: Maybe<&2, A>) -> Maybe<&2, A>:
65 match hit:
66 case True{}:
67 Some{v}
68 case False{}:
69 rest
70
71def lookup.entry(-A: Data, +s: String, e: Entry<A>, rest: Maybe<&2, A>) -> Maybe<&2, A>:
72 Entry{name, v} = e
73 lookup.hit(A, String.eq(s, name), v, rest)
74
75def lookup(-A: Data, +s: String, table: List<&2, Entry<A>>) -> Maybe<&2, A>:
76 match table:
77 case Nil{}:
78 None{}
79 case Con{e, rest}:
80 lookup.entry(A, s, e, lookup(A, s, rest))
81
82
83
84def defaults() -> Config:
85 Config{"", "", VG.Natural{}, VG.Undirected{}, VG.Unweighted{}, 0, EdgeList{}}
86
87def bad(name: String, value: String) -> Config:
88 Usage{"invalid value for " ++ name ++ ": " ++ value}
89
90def with_graph(found: Maybe<&2, VG.Visibility>, name: String, value: String, o: String, x: String, d: VG.Direction, k: VG.Weight, l: U32, m: Mode) -> Config:
91 match found:
92 case Some{g}:
93 Config{o, x, g, d, k, l, m}
94 case None{}:
95 bad(name, value)
96
97def with_direction(found: Maybe<&2, VG.Direction>, name: String, value: String, o: String, x: String, g: VG.Visibility, k: VG.Weight, l: U32, m: Mode) -> Config:
98 match found:
99 case Some{d}:
100 Config{o, x, g, d, k, l, m}
101 case None{}:
102 bad(name, value)
103
104def with_weight(found: Maybe<&2, VG.Weight>, name: String, value: String, o: String, x: String, g: VG.Visibility, d: VG.Direction, l: U32, m: Mode) -> Config:
105 match found:
106 case Some{k}:
107 Config{o, x, g, d, k, l, m}
108 case None{}:
109 bad(name, value)
110
111def with_limit(found: Maybe<&2, U32>, name: String, value: String, o: String, x: String, g: VG.Visibility, d: VG.Direction, k: VG.Weight, m: Mode) -> Config:
112 match found:
113 case Some{l}:
114 Config{o, x, g, d, k, l, m}
115 case None{}:
116 bad(name, value)
117
118def with_mode(found: Maybe<&2, Mode>, name: String, value: String, o: String, x: String, g: VG.Visibility, d: VG.Direction, k: VG.Weight, l: U32) -> Config:
119 match found:
120 case Some{m}:
121 Config{o, x, g, d, k, l, m}
122 case None{}:
123 bad(name, value)
124
125def set(cfg: Config, flag: Maybe<&2, Flag>, +name: String, +value: String) -> Config:
126 match cfg flag:
127 case Usage{msg} _:
128 Usage{msg}
129 case Config{o, x, g, d, k, l, m} None{}:
130 Usage{"unknown option: " ++ name}
131 case Config{o, x, g, d, k, l, m} Some{Output{}}:
132 Config{value, x, g, d, k, l, m}
133 case Config{o, x, g, d, k, l, m} Some{XFile{}}:
134 Config{o, value, g, d, k, l, m}
135 case Config{o, x, g, d, k, l, m} Some{GraphType{}}:
136 with_graph(lookup(VG.Visibility, value, graphs()), name, value, o, x, d, k, l, m)
137 case Config{o, x, g, d, k, l, m} Some{Directed{}}:
138 with_direction(lookup(VG.Direction, value, directions()), name, value, o, x, g, k, l, m)
139 case Config{o, x, g, d, k, l, m} Some{Weighted{}}:
140 with_weight(lookup(VG.Weight, value, weights()), name, value, o, x, g, d, l, m)
141 case Config{o, x, g, d, k, l, m} Some{Limit{}}:
142 with_limit(U32.read(value), name, value, o, x, g, d, k, m)
143 case Config{o, x, g, d, k, l, m} Some{OutputMode{}}:
144 with_mode(lookup(Mode, value, modes()), name, value, o, x, g, d, k, l)
145
146def parse(opts: List<String>, cfg: Config) -> Config:
147 match opts:
148 case Nil{}:
149 cfg
150 case Con{+name, Con{value, rest}}:
151 parse(rest, set(cfg, lookup(Flag, name, flags()), name, value))
152 case Con{name, Nil{}}:
153 Usage{"missing value for " ++ name}
154
155
156
157def output(mode: Mode, +k: VG.Weight, g: VG.Graph, +n: U32) -> String:
158 match mode:
159 case EdgeList{}:
160 Report.edge_list(Report.is_weighted(k), g)
161 case DegreeSequence{}:
162 Report.degree_sequence(n, Par.degrees(g))
163 case DegreeDistribution{}:
164 Report.degree_counts(True{}, n, Par.degrees(g))
165 case DegreeCounts{}:
166 Report.degree_counts(False{}, n, Par.degrees(g))
167
168def only_degrees(mode: Mode) -> Bool:
169 match mode:
170 case EdgeList{}:
171 False{}
172 case _:
173 True{}
174
175def graph_of(+g: VG.Visibility, +dir: VG.Direction, +k: VG.Weight, +limit: U32, ys: Array<F32>, xs: VG.Xs, +n: U32) -> VG.Graph:
176 Par.build(VG.Options{g, dir, k, VG.neg_inf(), VG.inf(), limit}, VG.Series{n, ys, xs})
177
178
179type XSource is Data:
180 ByIndex{}
181 Given{rev: List<&2, F32>}
182
183def xs_of(x: XSource, +n: U32) -> VG.Xs:
184 match x:
185 case ByIndex{}:
186 VG.Index{}
187 case Given{rev}:
188 VG.At{Series.ys(rev, n)}
189
190def report.built(weighted_degrees: Bool, +g: VG.Visibility, +dir: VG.Direction, +k: VG.Weight, +limit: U32, mode: Mode, rev: List<&2, F32>, x: XSource, +n: U32) -> Outcome:
191 match weighted_degrees:
192 case True{}:
193 Err{"Building with 'only_degrees' is only supported for unweighted graphs."}
194 case False{}:
195 Ok{output(mode, k, graph_of(g, dir, k, limit, Series.ys(rev, n), xs_of(x, n), n), n)}
196
197
198def report.sized(too_long: Bool, +g: VG.Visibility, +dir: VG.Direction, +k: VG.Weight, +limit: U32, +mode: Mode, rev: List<&2, F32>, +n: U32) -> Outcome:
199 match too_long:
200 case True{}:
201 Err{"bend_visgraph takes at most 16777216 values, since x is the index as an F32"}
202 case False{}:
203 report.built(only_degrees(mode) && Report.is_weighted(k), g, dir, k, limit, mode, rev, ByIndex{}, n)
204
205def report.xs(x: Series.XsCheck, +g: VG.Visibility, +dir: VG.Direction, +k: VG.Weight, +limit: U32, +mode: Mode, rev: List<&2, F32>, +n: U32) -> Outcome:
206 match x:
207 case Series.XsBad{msg}:
208 Err{msg}
209 case Series.XsOk{xrev}:
210 report.built(only_degrees(mode) && Report.is_weighted(k), g, dir, k, limit, mode, rev, Given{xrev}, n)
211
212
213type XsIn is Data:
214 NoXs{}
215 XsRead{p: Series.Parsed}
216
217def report(p: Series.Parsed, x: XsIn, +g: VG.Visibility, +dir: VG.Direction, +k: VG.Weight, +limit: U32, +mode: Mode) -> Outcome:
218 match p x:
219 case Series.Bad{line} _:
220 Err{"line " ++ U32.show(line) ++ " holds no number"}
221 case Series.Values{rev, +n} NoXs{}:
222 report.sized(U32.is_gt(n, 16777216), g, dir, k, limit, mode, rev, n)
223 case Series.Values{rev, +n} XsRead{xp}:
224 report.xs(Series.xs(xp, n), g, dir, k, limit, mode, rev, n)
225
226
227
228def write_text.done(m: File & Result<&1, &1, U32 & String, Unit>) -> IO(Unit):
229 (f, r) = m
230 do IO<Unit>:
231 done : Unit <- IO.pass(Unit, r)
232 File.close(f)
233
234def write_text(path: String, text: String) -> IO(Unit):
235 do IO<Unit>:
236 f : File <- IO.try(File, File.open(path, "w"))
237 m : File & Result<&1, &1, U32 & String, Unit> <- File.write(f, text)
238 write_text.done(m)
239
240def emit.to(stdout: Bool, path: String, text: String) -> IO(Unit):
241 match stdout:
242 case True{}:
243 IO.write(text)
244 case False{}:
245 write_text(path, text)
246
247def emit(+path: String, outcome: Outcome) -> IO(Unit):
248 match outcome:
249 case Ok{text}:
250 emit.to(String.is_empty(path), path, text)
251 case Err{msg}:
252 IO.die(Unit, 1, msg)
253
254def read_xs(none: Bool, path: String) -> IO(XsIn):
255 match none:
256 case True{}:
257 IO.pure(XsIn, NoXs{})
258 case False{}:
259 do IO<XsIn>:
260 text : String <- Series.read_text(path)
261 return XsRead{Series.parse(text)}
262
263def run(input: String, cfg: Config) -> IO(Unit):
264 match cfg:
265 case Usage{msg}:
266 IO.die(Unit, 2, msg ++ "\n" ++ usage())
267 case Config{+out, +xpath, g, dir, k, limit, mode}:
268 do IO<Unit>:
269 text : String <- Series.read_text(input)
270 x : XsIn <- read_xs(String.is_empty(xpath), xpath)
271 emit(out, report(Series.parse(text), x, g, dir, k, limit, mode))
272
273def start(args: List<String>) -> IO(Unit):
274 match args:
275 case Con{prog, Con{input, opts}}:
276 run(input, parse(opts, defaults()))
277 case _:
278 IO.die(Unit, 2, usage())
279
280def main() -> IO(Unit):
281 do IO<Unit>:
282 args : List<String> <- IO.args()
283 start(args)