Skip to content

Commit a46bf5f

Browse files
authored
Add Dijkstra in M4 (#5241)
1 parent b3c2a4a commit a46bf5f

File tree

1 file changed

+291
-0
lines changed

1 file changed

+291
-0
lines changed

archive/m/m4/dijkstra.m4

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
divert(-1)
2+
define(`show_usage',
3+
`Usage: please provide three inputs: a serialized matrix, a source node and a destination node
4+
m4exit(`1')')
5+
6+
dnl Reference: https://www.gnu.org/software/m4/manual/m4.html#index-array
7+
dnl array_get(varname, idx)
8+
define(`array_get', `defn(format(``%s[%s]'', `$1', `$2'))')
9+
10+
dnl array_set(varname, idx, value)
11+
define(`array_set', `define(format(``%s[%s]'', `$1', `$2'), `$3')')
12+
13+
dnl 2D versions of "array_get" and "array_set"
14+
dnl array2_get(varname, idx1, idx2)
15+
define(`array2_get', `defn(format(``%s[%s][%s]'', `$1', `$2', `$3'))')
16+
17+
dnl array2_set(varname, idx1, idx2, value)
18+
define(`array2_set', `define(format(``%s[%s][%s]'', `$1', `$2', `$3'), `$4')')
19+
20+
dnl 3D versions of "array_get" and "array_set"
21+
dnl array3_get(varname, idx1, idx2, idx3)
22+
define(`array3_get', `defn(format(``%s[%s][%s][%s]'', `$1', `$2', `$3', `$4'))')
23+
24+
dnl array3_set(varname, idx1, idx2, idx3, value)
25+
define(`array3_set', `define(format(``%s[%s][%s][%s]'', `$1', `$2', `$3', `$4'), `$5')')
26+
27+
dnl is_valid(n)
28+
define(`is_valid', `eval(regexp(`$1', `^\s*-?[0-9]+\s*$') >= 0)')
29+
30+
dnl parse_int_list(varname, args):
31+
dnl varname["length"] = 0
32+
dnl foreach arg in args:
33+
dnl if not is_valid(arg):
34+
dnl Return 0
35+
dnl varname[varname["length"]] = arg
36+
dnl varname["length"] = varname["length"] + 1
37+
dnl Return 1
38+
define(`parse_int_list',
39+
`array_set(`$1', `length', 0)dnl
40+
_parse_int_list(`$1', $2)'dnl
41+
)
42+
define(`_parse_int_list',
43+
`ifelse(is_valid(`$2'), 0, `0',
44+
`array_set(`$1', array_get(`$1', `length'), `$2')dnl
45+
array_set(`$1', `length', incr(array_get(`$1', `length')))dnl
46+
ifelse(eval($# > 2), 1, `_parse_int_list(`$1', shift(shift($@)))', `1')'dnl
47+
)'dnl
48+
)
49+
50+
dnl Reference: https://en.wikipedia.org/wiki/Integer_square_root#Algorithm_using_Newton's_method
51+
dnl isqrt(n):
52+
dnl if n < 2:
53+
dnl return n
54+
dnl prev2 = -1
55+
dnl prev1 = 1
56+
dnl while 1:
57+
dnl x1 = (prev1 + n // prev1) // 2
58+
dnl
59+
dnl // Case 1: converged (steady value)
60+
dnl if x1 == prev1:
61+
dnl return x1
62+
dnl
63+
dnl // Case 2: oscillation (2-cycle)
64+
dnl if x1 == prev2 and x1 != prev1:
65+
dnl return min(prev1, x1)
66+
dnl
67+
dnl // Set up next values
68+
dnl prev2, prev1 = prev1, x1
69+
define(`isqrt', `ifelse(eval($1 < 2), 1, $1,
70+
`pushdef(`x1', 0)dnl
71+
_isqrt($1, -1, 1)dnl
72+
popdef(`x1')dnl
73+
'dnl
74+
)'dnl
75+
)
76+
77+
dnl n=$1, prev2=$2, prev1=$3
78+
define(`_isqrt',
79+
`define(`x1', eval(($3 + $1 / $3) >> 1))dnl
80+
ifelse(eval(x1 == $3), 1, x1,
81+
`ifelse(eval(x1 == $2 && x1 != $3), 1, `min(`$3', x1)', `_isqrt(`$1', `$3', x1)')'dnl
82+
)'dnl
83+
)
84+
85+
dnl min(a, b)
86+
define(`min', `ifelse(eval($1 < $2), 1, $1, $2)')
87+
88+
dnl validate_inputs(weights_varname, num_vertices, src, dest):
89+
dnl // Verify number of weights is a square, source and destination are valid
90+
dnl if (weights_varname["length"] != num_vertices * num_vertices or
91+
dnl src < 0 or src >= num_vertices or dest < 0 or dest >= num_vertices):
92+
dnl return 0
93+
dnl
94+
dnl // Verify weights greater than equal to zero and any non-zero weights
95+
dnl any_non_zero = 0
96+
dnl for i = 0 to weights_varname["length"] - 1
97+
dnl if weights_varname[i] < 0:
98+
dnl return 0
99+
dnl
100+
dnl if weights_varname[i] > 0:
101+
dnl any_non_zero = 1
102+
dnl
103+
dnl return any_non_zero
104+
define(`validate_inputs',
105+
`ifelse(eval(array_get(`$1', `length') != $2 * $2 ||
106+
$3 < 0 || $3 >= $2 || $4 < 0 || $4 >= $2), 1, 0, `validate_weights(`$1', 0, 0)'dnl
107+
)'dnl
108+
)
109+
110+
dnl weights_varname=$1, i=$2, any_non_zero=$3
111+
define(`validate_weights',
112+
`ifelse(eval($2 >= array_get(`$1', `length')), 1, `$3',
113+
`ifelse(eval(array_get(`$1', `$2') < 0), 1, 0,
114+
eval(array_get(`$1', `$2') > 0), 1,
115+
`validate_weights(`$1', incr($2), 1)', `validate_weights(`$1', incr($2), `$3')'dnl
116+
)'dnl
117+
)'dnl
118+
)
119+
120+
dnl create_graph(weights_varname, num_vertices, graph_varname):
121+
dnl // Create nodes
122+
dnl graph_varname["length"] = num_vertices
123+
dnl for i = 0 to num_vertices - 1:
124+
dnl graph_varname[i]["length"] = 0
125+
dnl
126+
dnl // Connect children to nodes
127+
dnl idx = 0
128+
dnl for i = 0 to num_vertices - 1:
129+
dnl for j = 0 to num_vertices - 1:
130+
dnl if weight_mtx_varname[idx] > 0:
131+
dnl graph_varname[i]["edge"][graph_varname[i]["length"]] = j
132+
dnl graph_varname[i]["weight"][graph_varname[i]["length"]] = weights_varname[idx]
133+
dnl graph_varname[i]["length"] = graph_varname[i]["length"] + 1
134+
dnl
135+
dnl idx = idx + 1
136+
define(`create_graph',
137+
`array_set(`$3', `length', `$2')dnl
138+
_create_nodes(`$3', `$2', 0)dnl
139+
_connect_children(`$1', `$2', `$3', 0, 0)dnl
140+
'dnl
141+
)
142+
143+
dnl graph_varname=$1, num_vertices=$2, i=$3
144+
define(`_create_nodes',
145+
`ifelse(eval($3 < $2), 1,
146+
`array2_set(`$1', `$3', `length', 0)dnl
147+
_create_nodes(`$1', `$2', incr($3))'dnl
148+
)'dnl
149+
)
150+
151+
dnl weights_varname=$1, num_vertices=$2, graph_varname=$3, i=$4, idx=$5
152+
define(`_connect_children',
153+
`ifelse(eval($4 < $2), 1,
154+
`_connect_children_inner(`$1', `$2', `$3', `$4', `$5', 0)dnl
155+
_connect_children(`$1', `$2', `$3', incr($4), eval($5 + $2))'dnl
156+
)'dnl
157+
)
158+
159+
dnl weights_varname=$1, num_vertices=$2, graph_varname=$3, i=$4, idx=$5, j=$6
160+
define(`_connect_children_inner',
161+
`ifelse(eval($6 < $2), 1,
162+
`ifelse(eval(array_get(`$1', `$5') > 0), 1,
163+
`array3_set(`$3', `$4', `edge', array2_get(`$3', `$4', `length'), `$6')dnl
164+
array3_set(`$3', `$4', `weight', array2_get(`$3', `$4', `length'), array_get(`$1', $5))dnl
165+
array2_set(`$3', `$4', `length', incr(array2_get(`$3', `$4', `length')))'dnl
166+
)dnl
167+
_connect_children_inner(`$1', `$2', `$3', `$4', incr($5), incr($6))'dnl
168+
)'dnl
169+
)
170+
171+
dnl M4 does not have infinity so choose largest integer value as infinity
172+
define(`INF', 2147483647)
173+
174+
dnl Source: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm#Pseudocode
175+
dnl dijkstra(graph_varname, src, dists_varname):
176+
dnl // Note: unvisited (q) is a string that looks like this: "|<node1>||<node2>|..."
177+
dnl // Since M4 does not have anything like a set, this is the easiest way to
178+
dnl // represent it, and the "index" function can be used to see if a node is
179+
dnl // unvisited (non-negative if unvisited)
180+
dnl
181+
dnl // Initialize distances to infinite and initialize unvisited nodes
182+
dnl q = ""
183+
dnl dists_varname["length"] = graph_varname["length"]
184+
dnl for i = 0 to dists_varname["length"] - 1:
185+
dnl dists_varname[i] = INF
186+
dnl q = q + "|" + i + "|"
187+
dnl
188+
dnl // Set first vertex distance to 0
189+
dnl dists_varname[src] = 0
190+
dnl
191+
dnl // While any unvisited nodes
192+
dnl while q != "":
193+
dnl // Pick a vertex u in q with minimum distance
194+
dnl u = find_min_distance(dists_varname)
195+
dnl
196+
dnl // Remove vertex u from q
197+
dnl q = replace(q, "|" + u + "|", "")
198+
dnl
199+
dnl // For each neighbor v of vertex u still in q
200+
dnl for i = 0 to graph[u]["length"] - 1:
201+
dnl v = graph_varname[u]["edge"][i]
202+
dnl if ("|" + v + "|) in q:
203+
dnl // Get trial distance. If it is smaller than distance v, update distance to v
204+
dnl alt = add(dists_varname[u], graph_varname[u]["weight"][i])
205+
dnl if alt < dists_varname[v]:
206+
dnl dists_varname[v] = alt
207+
define(`dijkstra',
208+
`pushdef(`q', `')dnl
209+
pushdef(`u', `')dnl
210+
pushdef(`v', `')dnl
211+
pushdef(`alt', `')dnl
212+
array_set(`$3', `length', array_get(`$1', `length'))dnl
213+
_init_dijkstra_result(`$3', 0)dnl
214+
array_set(`$3', `$2', 0)dnl
215+
_dijkstra_outer(`$1', `$3')dnl
216+
popdef(`alt')dnl
217+
popdef(`v')dnl
218+
popdef(`u')dnl
219+
popdef(`q')dnl
220+
')
221+
222+
dnl dists_varname=$1, i=$2
223+
define(`_init_dijkstra_result',
224+
`ifelse(eval($2 < array_get(`$1', `length')), 1,
225+
`array_set(`$1', `$2', INF)dnl
226+
define(`q', q`|$2|')dnl
227+
_init_dijkstra_result(`$1', incr($2))'dnl
228+
)'dnl
229+
)
230+
231+
dnl graph_varname=$1, dists_varname=$2
232+
define(`_dijkstra_outer',
233+
`ifelse(q, `',,
234+
`define(`u', find_min_distance(`$2'))dnl
235+
define(`q', patsubst(q, `|'u`|'))dnl
236+
_dijkstra_inner(`$1', `$2', 0)dnl
237+
_dijkstra_outer(`$1', `$2')dnl
238+
'dnl
239+
)'dnl
240+
)
241+
242+
dnl graph_varname=$1, dists_varname=$2, i=$3
243+
define(`_dijkstra_inner',
244+
`ifelse(eval($3 < array2_get(`$1', u, `length')), 1,
245+
`define(`v', array3_get(`$1', u, `edge', `$3'))dnl
246+
ifelse(eval(index(q, `|'v`|') >= 0), 1,
247+
`define(`alt', add(array_get(`$2', u), array3_get(`$1', u, `weight', `$3')))dnl
248+
ifelse(eval(alt < array_get(`$2', v)), 1, `array_set(`$2', v, alt)')dnl
249+
')dnl
250+
_dijkstra_inner(`$1', `$2', incr($3))dnl
251+
'dnl
252+
)'dnl
253+
)
254+
255+
dnl find_min_distance(dists_varname):
256+
dnl min_dist = INF
257+
dnl min_index = -1
258+
dnl for i = 0 to dists_varname["length"] - 1:
259+
dnl if ("|" + i + "|") in q and dists_varname[i] < min_dist:
260+
dnl min_dist = dists_varname[i]
261+
dnl min_index = i
262+
dnl return min_index
263+
define(`find_min_distance', `_find_min_distance(`$1', INF, -1, 0)')
264+
265+
dnl dists_varname=$1, min_dist=$2, min_index=$3, i=$4
266+
define(`_find_min_distance',
267+
`ifelse(eval($4 >= array_get(`$1', `length')), 1, `$3',
268+
`ifelse(eval(index(q, `|$4|') >= 0 && array_get(`$1', `$4') < $2), 1,
269+
`_find_min_distance(`$1', array_get(`$1', `$4'), `$4', incr($4))',
270+
`_find_min_distance(`$1', `$2', `$3', incr($4))'dnl
271+
)'dnl
272+
)'dnl
273+
)
274+
275+
dnl add(a, b):
276+
dnl if a == INF or b == INF:
277+
dnl return INF
278+
dnl return a + b
279+
define(`add', `ifelse(eval($1 == INF || $2 == INF), 1, INF, `eval($1 + $2)')')
280+
281+
divert(0)dnl
282+
ifelse(eval(ARGC < 3 ||
283+
!parse_int_list(`weights', ARGV1) ||
284+
!is_valid(ARGV2) ||
285+
!is_valid(ARGV3)
286+
), 1, `show_usage()')dnl
287+
define(`num_vertices', isqrt(array_get(`weights', `length')))dnl
288+
ifelse(validate_inputs(weights, num_vertices, ARGV2, ARGV3), 0, `show_usage()')dnl
289+
create_graph(`weights', num_vertices, `graph')dnl
290+
dijkstra(`graph', ARGV2, `dists')dnl
291+
array_get(`dists', ARGV3)

0 commit comments

Comments
 (0)