|
27 | 27 | * Graph execution functions |
28 | 28 | * ============================================================================ */ |
29 | 29 |
|
| 30 | +/* Precondition for algorithms that iterate node ids in [0, fwd.n_nodes) while |
| 31 | + * dereferencing the reverse CSR at the same index (e.g. rev_off[i+1]). A |
| 32 | + * relation built with distinct src/dst node counts (ray_rel_from_edges with |
| 33 | + * n_src_nodes != n_dst_nodes) has fwd.n_nodes != rev.n_nodes; indexing the |
| 34 | + * smaller CSR's offset array with the larger bound reads out of bounds. |
| 35 | + * |
| 36 | + * Returns NULL when the relation is square (safe), otherwise a loud error |
| 37 | + * naming the op. Callers must propagate the returned error. */ |
| 38 | +static ray_t* graph_require_square(const ray_rel_t* rel, const char* op_name) { |
| 39 | + if (rel->fwd.n_nodes != rel->rev.n_nodes) |
| 40 | + return ray_error("domain", |
| 41 | + "%s: requires a square relation (forward and reverse node counts " |
| 42 | + "must match), got fwd=%lld rev=%lld", |
| 43 | + op_name, (long long)rel->fwd.n_nodes, (long long)rel->rev.n_nodes); |
| 44 | + return NULL; |
| 45 | +} |
| 46 | + |
30 | 47 | /* exec_expand_factorized: emit factorized output for expand+group fusion. |
31 | 48 | * Returns a table with _src (unique sources) and _count (degree per source). |
32 | 49 | * This avoids materializing the full (src, dst) cross-product. */ |
@@ -293,6 +310,11 @@ ray_t* exec_var_expand(ray_graph_t* g, ray_op_t* op, ray_t* start_vec) { |
293 | 310 | uint8_t direction = ext->graph.direction; |
294 | 311 | uint8_t min_depth = ext->graph.min_depth; |
295 | 312 | uint8_t max_depth = ext->graph.max_depth; |
| 313 | + /* path_tracking is accepted by the op constructor but unimplemented: the |
| 314 | + * BFS records (start, end, depth) endpoints only, not parent chains. |
| 315 | + * Surface a loud error instead of silently dropping the request. */ |
| 316 | + if (ext->graph.path_tracking) |
| 317 | + return ray_error("nyi", "graph.var-expand: path tracking is not supported (only start/end/depth endpoints are returned)"); |
296 | 318 | ray_csr_t* csr_fwd = &rel->fwd; |
297 | 319 | ray_csr_t* csr_rev = &rel->rev; |
298 | 320 | /* For direction==2 (both), use fwd for n_nodes bound but expand both */ |
@@ -651,6 +673,7 @@ ray_t* exec_pagerank(ray_graph_t* g, ray_op_t* op) { |
651 | 673 | double damping = ext->graph.damping; |
652 | 674 |
|
653 | 675 | if (n <= 0) return ray_error("length", "graph.pagerank: graph must have at least one node, got %lld", (long long)n); |
| 676 | + { ray_t* sq = graph_require_square(rel, "graph.pagerank"); if (sq) return sq; } |
654 | 677 |
|
655 | 678 | /* Arena for all scratch memory — freed in one shot */ |
656 | 679 | ray_scratch_arena_t arena; |
@@ -752,6 +775,7 @@ ray_t* exec_connected_comp(ray_graph_t* g, ray_op_t* op) { |
752 | 775 |
|
753 | 776 | int64_t n = rel->fwd.n_nodes; |
754 | 777 | if (n <= 0) return ray_error("length", "graph.connected: graph must have at least one node, got %lld", (long long)n); |
| 778 | + { ray_t* sq = graph_require_square(rel, "graph.connected"); if (sq) return sq; } |
755 | 779 |
|
756 | 780 | /* Arena for all scratch memory — freed in one shot */ |
757 | 781 | ray_scratch_arena_t arena; |
@@ -939,6 +963,7 @@ ray_t* exec_dijkstra(ray_graph_t* g, ray_op_t* op, |
939 | 963 |
|
940 | 964 | int64_t n = rel->fwd.n_nodes; |
941 | 965 | int64_t m = rel->fwd.n_edges; |
| 966 | + uint8_t max_depth = ext->graph.max_depth; |
942 | 967 | int64_t src_id = ray_is_atom(src_val) ? src_val->i64 : ((int64_t*)ray_data(src_val))[0]; |
943 | 968 | int64_t dst_id = !dst_val ? -1 : ray_is_atom(dst_val) ? dst_val->i64 : ((int64_t*)ray_data(dst_val))[0]; |
944 | 969 |
|
@@ -1000,6 +1025,11 @@ ray_t* exec_dijkstra(ray_graph_t* g, ray_op_t* op, |
1000 | 1025 |
|
1001 | 1026 | if (u == dst_id) break; /* early exit if destination reached */ |
1002 | 1027 |
|
| 1028 | + /* Honor max-depth (hop bound): do not relax beyond max_depth hops |
| 1029 | + * from the source. depth[] is the natural hop counter. A path |
| 1030 | + * needing more than max_depth hops is simply not discovered. */ |
| 1031 | + if (depth[u] >= max_depth) continue; |
| 1032 | + |
1003 | 1033 | for (int64_t j = fwd_off[u]; j < fwd_off[u + 1]; j++) { |
1004 | 1034 | int64_t v = fwd_tgt[j]; |
1005 | 1035 | int64_t edge_row = fwd_row[j]; |
@@ -1179,6 +1209,7 @@ ray_t* exec_louvain(ray_graph_t* g, ray_op_t* op) { |
1179 | 1209 | uint16_t max_iter = ext->graph.max_iter; |
1180 | 1210 |
|
1181 | 1211 | if (n <= 0) return ray_error("length", "graph.louvain: graph must have at least one node, got %lld", (long long)n); |
| 1212 | + { ray_t* sq = graph_require_square(rel, "graph.louvain"); if (sq) return sq; } |
1182 | 1213 |
|
1183 | 1214 | /* Arena for all scratch memory — freed in one shot */ |
1184 | 1215 | ray_scratch_arena_t arena; |
@@ -1331,6 +1362,7 @@ ray_t* exec_degree_cent(ray_graph_t* g, ray_op_t* op) { |
1331 | 1362 |
|
1332 | 1363 | int64_t n = rel->fwd.n_nodes; |
1333 | 1364 | if (n <= 0) return ray_error("length", "graph.degree: graph must have at least one node, got %lld", (long long)n); |
| 1365 | + { ray_t* sq = graph_require_square(rel, "graph.degree"); if (sq) return sq; } |
1334 | 1366 |
|
1335 | 1367 | int64_t* fwd_off = (int64_t*)ray_data(rel->fwd.offsets); |
1336 | 1368 | int64_t* rev_off = (int64_t*)ray_data(rel->rev.offsets); |
@@ -1397,6 +1429,7 @@ ray_t* exec_topsort(ray_graph_t* g, ray_op_t* op) { |
1397 | 1429 |
|
1398 | 1430 | int64_t n = rel->fwd.n_nodes; |
1399 | 1431 | if (n <= 0) return ray_error("length", "graph.topsort: graph must have at least one node, got %lld", (long long)n); |
| 1432 | + { ray_t* sq = graph_require_square(rel, "graph.topsort"); if (sq) return sq; } |
1400 | 1433 |
|
1401 | 1434 | int64_t* fwd_off = (int64_t*)ray_data(rel->fwd.offsets); |
1402 | 1435 | int64_t* fwd_tgt = (int64_t*)ray_data(rel->fwd.targets); |
@@ -1490,6 +1523,7 @@ ray_t* exec_cluster_coeff(ray_graph_t* g, ray_op_t* op) { |
1490 | 1523 |
|
1491 | 1524 | int64_t n = rel->fwd.n_nodes; |
1492 | 1525 | if (n <= 0) return ray_error("length", "graph.cluster: graph must have at least one node, got %lld", (long long)n); |
| 1526 | + { ray_t* sq = graph_require_square(rel, "graph.cluster"); if (sq) return sq; } |
1493 | 1527 |
|
1494 | 1528 | ray_scratch_arena_t arena; |
1495 | 1529 | ray_scratch_arena_init(&arena); |
@@ -1592,6 +1626,7 @@ ray_t* exec_betweenness(ray_graph_t* g, ray_op_t* op) { |
1592 | 1626 |
|
1593 | 1627 | int64_t n = rel->fwd.n_nodes; |
1594 | 1628 | if (n <= 0) return ray_error("length", "graph.betweenness: graph must have at least one node, got %lld", (long long)n); |
| 1629 | + { ray_t* sq = graph_require_square(rel, "graph.betweenness"); if (sq) return sq; } |
1595 | 1630 | uint16_t sample = ext->graph.max_iter; |
1596 | 1631 | int64_t n_sources = (sample > 0 && (int64_t)sample < n) ? (int64_t)sample : n; |
1597 | 1632 |
|
@@ -1778,6 +1813,7 @@ ray_t* exec_closeness(ray_graph_t* g, ray_op_t* op) { |
1778 | 1813 |
|
1779 | 1814 | int64_t n = rel->fwd.n_nodes; |
1780 | 1815 | if (n <= 0) return ray_error("length", "graph.closeness: graph must have at least one node, got %lld", (long long)n); |
| 1816 | + { ray_t* sq = graph_require_square(rel, "graph.closeness"); if (sq) return sq; } |
1781 | 1817 | uint16_t sample = ext->graph.max_iter; |
1782 | 1818 | int64_t n_sources = (sample > 0 && (int64_t)sample < n) ? (int64_t)sample : n; |
1783 | 1819 |
|
@@ -2224,6 +2260,7 @@ ray_t* exec_astar(ray_graph_t* g, ray_op_t* op, |
2224 | 2260 |
|
2225 | 2261 | int64_t n = rel->fwd.n_nodes; |
2226 | 2262 | int64_t m = rel->fwd.n_edges; |
| 2263 | + uint8_t max_depth = ext->graph.max_depth; |
2227 | 2264 | int64_t src_id = src_val->i64; |
2228 | 2265 | int64_t dst_id = dst_val->i64; |
2229 | 2266 |
|
@@ -2282,6 +2319,9 @@ ray_t* exec_astar(ray_graph_t* g, ray_op_t* op, |
2282 | 2319 |
|
2283 | 2320 | if (u == dst_id) break; |
2284 | 2321 |
|
| 2322 | + /* Honor max-depth (hop bound): stop expanding past max_depth hops. */ |
| 2323 | + if (depth_a[u] >= max_depth) continue; |
| 2324 | + |
2285 | 2325 | for (int64_t j = fwd_off[u]; j < fwd_off[u + 1]; j++) { |
2286 | 2326 | int64_t v = fwd_tgt[j]; |
2287 | 2327 | int64_t edge_row = fwd_row[j]; |
|
0 commit comments