Skip to content

Commit c59368b

Browse files
committed
make examples more helpful
1 parent 5c70791 commit c59368b

File tree

4 files changed

+114
-23
lines changed

4 files changed

+114
-23
lines changed

examples/graphs/bfs_bottomup.py

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,65 @@
1+
import argparse
2+
from collections import deque
3+
4+
import numpy as np_cpu
5+
16
import pykokkos as pk
27

38
if pk.get_default_space() in pk.DeviceExecutionSpace:
49
import cupy as np
510
else:
611
import numpy as np
712

8-
import argparse
13+
14+
def _view_to_numpy_host(x):
15+
"""Host NumPy array for comparisons (handles Cupy-backed views)."""
16+
if hasattr(x, "get"):
17+
return np_cpu.asarray(x.get())
18+
return np_cpu.asarray(x)
19+
20+
21+
def reference_grid_bfs_distances(N: int, M: int, mat) -> np_cpu.ndarray:
22+
"""
23+
Shortest hop count (4-neighbor grid) from every cell to any cell with mat==0.
24+
Same graph as the PyKokkos workunits: vertices are grid cells, edges to N/E/S/W
25+
neighbors. Multi-source BFS using a queue, following the standard pattern in
26+
https://www.geeksforgeeks.org/python/python-program-for-breadth-first-search-or-bfs-for-a-graph/
27+
"""
28+
mat_h = _view_to_numpy_host(mat)
29+
dist = np_cpu.full(N * M, -1, dtype=np_cpu.int32)
30+
q = deque()
31+
for r in range(N):
32+
for c in range(M):
33+
if mat_h[r, c] == 0:
34+
idx = r * M + c
35+
dist[idx] = 0
36+
q.append((r, c))
37+
while q:
38+
r, c = q.popleft()
39+
d = int(dist[r * M + c])
40+
for dr, dc in ((-1, 0), (1, 0), (0, -1), (0, 1)):
41+
nr, nc = r + dr, c + dc
42+
if 0 <= nr < N and 0 <= nc < M:
43+
ni = nr * M + nc
44+
if dist[ni] == -1:
45+
dist[ni] = d + 1
46+
q.append((nr, nc))
47+
return dist.astype(np_cpu.float64)
48+
49+
50+
def assert_bfs_matches_pykokkos(N: int, M: int, mat, val, max_arr) -> None:
51+
ref = reference_grid_bfs_distances(N, M, mat)
52+
val_h = _view_to_numpy_host(val)
53+
max_h = float(_view_to_numpy_host(max_arr)[0])
54+
if not np_cpu.allclose(val_h, ref, rtol=0.0, atol=1e-9):
55+
bad = np_cpu.where(np_cpu.abs(val_h - ref) > 1e-9)[0][:16]
56+
raise AssertionError(
57+
f"distance mismatch at linear indices (first few): {bad.tolist()}"
58+
)
59+
ref_max = float(np_cpu.max(ref))
60+
if not np_cpu.isclose(max_h, ref_max, rtol=0.0, atol=1e-9):
61+
raise AssertionError(f"max distance mismatch: got {max_h}, expected {ref_max}")
62+
print("BFS correctness check: OK (matches queue-based NumPy reference)")
963

1064

1165
def main(N: int, M: int):
@@ -29,6 +83,8 @@ def main(N: int, M: int):
2983
pk.parallel_for(N, extend2D, N=N, max_arr=max_arr, max_arr2D=max_arr2D)
3084
pk.parallel_for(N, reduce1D, N=N, max_arr=max_arr, max_arr2D=max_arr2D)
3185

86+
assert_bfs_matches_pykokkos(N, M, mat, val, max_arr)
87+
3288
print(f"\ndistance of every cell:\n")
3389
for i in range(element):
3490
print(f"val ({val[i]}) ", end="")
@@ -73,21 +129,21 @@ def check_vis(
73129
if min_val > val[i - M]:
74130
min_val = val[i - M]
75131

76-
# check the neighbor on the next row
132+
# check the neighbor on the next row
77133
if i // M < (N - 1):
78134
if visited[i + M] == 1:
79135
flag = 1
80136
if min_val > val[i + M]:
81137
min_val = val[i + M]
82138

83-
# check the neighbor on the left
139+
# check the neighbor on the left
84140
if i % M > 0:
85141
if visited[i - 1] == 1:
86142
flag = 1
87143
if min_val > val[i - 1]:
88144
min_val = val[i - 1]
89145

90-
# check the neighbor on the right
146+
# check the neighbor on the right
91147
if i % M < (M - 1):
92148
if visited[i + 1] == 1:
93149
flag = 1
@@ -96,10 +152,10 @@ def check_vis(
96152

97153
# if there is at least one neighbor visited, the value of
98154
# the current index can be updated and should be marked as visited
99-
if flag == 1:
100-
if val[i] > min_val:
101-
val[i] = min_val + 1
102-
visited[i] = 1
155+
if flag == 1:
156+
if val[i] > min_val:
157+
val[i] = min_val + 1
158+
visited[i] = 1
103159

104160
################################
105161
# findmax will find the maximum value of cell in each row

examples/pykokkos/binsort.py

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@ def main():
1212
view = np.zeros(total_threads, dtype=np.int32)
1313

1414
pk.parallel_for(total_threads, work, total_threads=total_threads, view=view)
15+
max_bins = total_threads // 2
16+
min_key = total_threads
17+
max_key = total_threads * 2 - 1
1518
bin_op = pk.BinOp1D(
1619
view,
17-
(total_threads // 2),
18-
total_threads,
19-
total_threads * 2 - 1,
20+
max_bins,
21+
min_key,
22+
max_key,
2023
)
2124
bin_sort = pk.BinSort(view, bin_op)
2225
bin_sort.create_permute_vector()
@@ -25,10 +28,31 @@ def main():
2528
bin_count = bin_sort.get_bin_count()
2629
bin_sort.sort(view)
2730

28-
print(view)
29-
print(permute_vector)
30-
print(bin_offsets)
31-
print(bin_count)
31+
print(
32+
"PyKokkos BinSort demo: fill a 1D key view on the device, bucket keys into "
33+
f"{max_bins} bins over [{min_key}, {max_key}], then sort.\n"
34+
f" Initial keys: view[i] = i + {total_threads} (see work unit).\n"
35+
)
36+
print(
37+
"Sorted keys (same 1D view, after bin_sort.sort(view) — in-place reorder):\n",
38+
view,
39+
"\n",
40+
)
41+
print(
42+
"Permute vector from create_permute_vector / get_permute_vector — "
43+
"indices describing how elements were reordered:\n",
44+
permute_vector,
45+
"\n",
46+
)
47+
print(
48+
"Bin offsets (get_bin_offsets) — start index of each bin in the sorted layout:\n",
49+
bin_offsets,
50+
"\n",
51+
)
52+
print(
53+
"Bin counts (get_bin_count) — number of keys in each bin:\n",
54+
bin_count,
55+
)
3256

3357

3458
@pk.workunit

examples/pykokkos/classtypes.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,16 @@ def test(self) -> float:
1111

1212

1313
@pk.workunit
14-
def work(tid: int):
15-
pk.printf("%d\n", tid)
14+
def work(tid: int, acc: pk.Acc[pk.double]) -> None:
15+
tc: TestClass = TestClass(float(tid))
16+
acc += tc.test()
1617

1718

1819
def main():
1920
total_threads: int = 10
20-
pk.parallel_for(total_threads, work)
21+
result: float = pk.parallel_reduce(total_threads, work)
22+
expected: float = sum(2.0 * float(i) for i in range(total_threads))
23+
print(f"parallel_reduce: {result} (expected {expected})")
2124

2225

2326
if __name__ == "__main__":

examples/pykokkos/subviews.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import numpy as np
21
import pykokkos as pk
32

43
if pk.get_default_space() in pk.DeviceExecutionSpace:
@@ -8,15 +7,24 @@
87

98

109
def main():
11-
view = np.zeros((10, 10), dtype=np.int32)
12-
subview = view[3, 2:5]
13-
pk.parallel_for(10, work, view=view)
10+
n: int = 10
11+
# 2D view; each thread takes a row subview and a column-range subview inside the workunit.
12+
view = np.zeros((n, n), dtype=np.int32)
13+
pk.parallel_for(n, work, view=view)
14+
print(
15+
"PyKokkos subviews: each iteration builds Kokkos::subview row = view[i, :] and "
16+
"band = view[i, 2:5], then writes through those 1D subviews (not view[i][j] alone).\n"
17+
)
1418
print(view)
1519

1620

1721
@pk.workunit
1822
def work(i: int, view: pk.View2D[pk.int32]):
19-
view[i][i] = 1
23+
# Plain `name = view[...]` becomes Kokkos::subview (annotated assign does not).
24+
row = view[i, :]
25+
band = view[i, 2:5]
26+
row[i] = 1
27+
band[i % 3] = i + 1
2028

2129

2230
if __name__ == "__main__":

0 commit comments

Comments
 (0)