1use std::cmp::Reverse;
10use std::collections::BinaryHeap;
11
12use crate::error::{GraphError, Result};
13
14pub(super) use super::parallel::{parallel_bellman_ford_atomic, parallel_bfs_atomic};
15
16const GPU_EDGE_THRESHOLD: usize = 4096;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21#[non_exhaustive]
22pub enum GpuGraphBackend {
23 CpuParallel,
25 Gpu,
27}
28
29#[derive(Debug, Clone)]
31pub struct GpuBfsConfig {
32 pub backend: GpuGraphBackend,
34 pub chunk_size: usize,
36}
37
38impl Default for GpuBfsConfig {
39 fn default() -> Self {
40 Self {
41 backend: GpuGraphBackend::CpuParallel,
42 chunk_size: 1024,
43 }
44 }
45}
46
47pub fn gpu_bfs(
60 row_ptr: &[usize],
61 col_idx: &[usize],
62 source: usize,
63 config: &GpuBfsConfig,
64) -> Result<Vec<usize>> {
65 if row_ptr.len() < 2 {
66 return Err(GraphError::InvalidParameter {
67 param: "row_ptr".to_string(),
68 value: format!("len={}", row_ptr.len()),
69 expected: "at least 2 elements (n+1)".to_string(),
70 context: "gpu_bfs".to_string(),
71 });
72 }
73
74 let n = row_ptr.len() - 1;
75
76 if source >= n {
77 return Err(GraphError::InvalidParameter {
78 param: "source".to_string(),
79 value: format!("{source}"),
80 expected: format!("0..{n}"),
81 context: "gpu_bfs".to_string(),
82 });
83 }
84
85 if let Some(&last) = row_ptr.last() {
86 if last > col_idx.len() {
87 return Err(GraphError::InvalidParameter {
88 param: "row_ptr/col_idx".to_string(),
89 value: format!("row_ptr last={}, col_idx len={}", last, col_idx.len()),
90 expected: "row_ptr[n] <= col_idx.len()".to_string(),
91 context: "gpu_bfs".to_string(),
92 });
93 }
94 }
95
96 match config.backend {
97 GpuGraphBackend::CpuParallel => Ok(parallel_bfs_atomic(row_ptr, col_idx, source)),
98 GpuGraphBackend::Gpu => {
99 #[cfg(feature = "wgpu")]
100 {
101 let n_edges = col_idx.len();
102 if n_edges >= GPU_EDGE_THRESHOLD {
103 match wgpu_bfs(row_ptr, col_idx, source) {
104 Ok(dist) => return Ok(dist),
105 Err(_) => {
106 }
108 }
109 }
110 }
111 Ok(parallel_bfs_atomic(row_ptr, col_idx, source))
113 }
114 }
115}
116
117pub fn gpu_sssp_bellman_ford(
134 row_ptr: &[usize],
135 col_idx: &[usize],
136 weights: &[f64],
137 source: usize,
138 config: &GpuBfsConfig,
139) -> Result<Vec<f64>> {
140 if row_ptr.len() < 2 {
141 return Err(GraphError::InvalidParameter {
142 param: "row_ptr".to_string(),
143 value: format!("len={}", row_ptr.len()),
144 expected: "at least 2 elements (n+1)".to_string(),
145 context: "gpu_sssp_bellman_ford".to_string(),
146 });
147 }
148
149 let n = row_ptr.len() - 1;
150
151 if source >= n {
152 return Err(GraphError::InvalidParameter {
153 param: "source".to_string(),
154 value: format!("{source}"),
155 expected: format!("0..{n}"),
156 context: "gpu_sssp_bellman_ford".to_string(),
157 });
158 }
159
160 if col_idx.len() != weights.len() {
161 return Err(GraphError::InvalidParameter {
162 param: "weights".to_string(),
163 value: format!("len={}", weights.len()),
164 expected: format!("same length as col_idx ({})", col_idx.len()),
165 context: "gpu_sssp_bellman_ford".to_string(),
166 });
167 }
168
169 let has_negative = weights.iter().any(|&w| w < 0.0);
170
171 match config.backend {
172 GpuGraphBackend::CpuParallel => {
173 if has_negative {
174 bellman_ford_sequential(row_ptr, col_idx, weights, source, n)
176 } else {
177 let weights_f32: Vec<f32> = weights.iter().map(|&w| w as f32).collect();
179 match parallel_bellman_ford_atomic(row_ptr, col_idx, &weights_f32, source) {
180 Ok(result) => Ok(result.into_iter().map(|v| v as f64).collect()),
181 Err(_) => bellman_ford_sequential(row_ptr, col_idx, weights, source, n),
182 }
183 }
184 }
185 GpuGraphBackend::Gpu => {
186 #[cfg(feature = "wgpu")]
187 {
188 let n_edges = col_idx.len();
189 if !has_negative && n_edges >= GPU_EDGE_THRESHOLD {
190 let weights_f32: Vec<f32> = weights.iter().map(|&w| w as f32).collect();
191 match wgpu_bellman_ford(row_ptr, col_idx, &weights_f32, source, n) {
192 Ok(dist_f32) => {
193 return Ok(dist_f32.into_iter().map(|v| v as f64).collect());
194 }
195 Err(_) => {
196 }
198 }
199 }
200 }
201 bellman_ford_sequential(row_ptr, col_idx, weights, source, n)
203 }
204 }
205}
206
207fn bellman_ford_sequential(
209 row_ptr: &[usize],
210 col_idx: &[usize],
211 weights: &[f64],
212 source: usize,
213 n: usize,
214) -> Result<Vec<f64>> {
215 let has_negative = weights.iter().any(|&w| w < 0.0);
216
217 let mut dist = vec![f64::INFINITY; n];
218 dist[source] = 0.0;
219
220 for _ in 0..(n.saturating_sub(1)) {
221 let mut changed = false;
222 for u in 0..n {
223 if dist[u] == f64::INFINITY {
224 continue;
225 }
226 let start = row_ptr[u];
227 let end = row_ptr[u + 1];
228 for idx in start..end {
229 let v = col_idx[idx];
230 let w = weights[idx];
231 let new_dist = dist[u] + w;
232 if new_dist < dist[v] {
233 dist[v] = new_dist;
234 changed = true;
235 }
236 }
237 }
238 if !changed {
239 break;
240 }
241 }
242
243 if has_negative {
245 for u in 0..n {
246 if dist[u] == f64::INFINITY {
247 continue;
248 }
249 let start = row_ptr[u];
250 let end = row_ptr[u + 1];
251 for idx in start..end {
252 let v = col_idx[idx];
253 let w = weights[idx];
254 if dist[u] + w < dist[v] {
255 return Err(GraphError::AlgorithmFailure {
256 algorithm: "gpu_sssp_bellman_ford".to_string(),
257 reason: "negative weight cycle detected".to_string(),
258 iterations: n,
259 tolerance: 0.0,
260 });
261 }
262 }
263 }
264 }
265
266 Ok(dist)
267}
268
269pub fn gpu_sssp_delta_stepping(
292 adj: &[Vec<(usize, f64)>],
293 source: usize,
294 delta: f64,
295 config: &GpuBfsConfig,
296) -> Result<Vec<f64>> {
297 let n = adj.len();
298
299 if n == 0 {
300 return Err(GraphError::InvalidParameter {
301 param: "adj".to_string(),
302 value: "len=0".to_string(),
303 expected: "non-empty graph".to_string(),
304 context: "gpu_sssp_delta_stepping".to_string(),
305 });
306 }
307
308 if source >= n {
309 return Err(GraphError::InvalidParameter {
310 param: "source".to_string(),
311 value: format!("{source}"),
312 expected: format!("0..{n}"),
313 context: "gpu_sssp_delta_stepping".to_string(),
314 });
315 }
316
317 if delta <= 0.0 {
318 return Err(GraphError::InvalidParameter {
319 param: "delta".to_string(),
320 value: format!("{delta}"),
321 expected: "positive value".to_string(),
322 context: "gpu_sssp_delta_stepping".to_string(),
323 });
324 }
325
326 for (u, nbrs) in adj.iter().enumerate() {
328 for &(_, w) in nbrs {
329 if w < 0.0 {
330 return Err(GraphError::InvalidParameter {
331 param: "weights".to_string(),
332 value: format!("negative weight on edge from {u}"),
333 expected: "non-negative edge weights for delta-stepping".to_string(),
334 context: "gpu_sssp_delta_stepping".to_string(),
335 });
336 }
337 }
338 }
339
340 let mut row_ptr = vec![0usize; n + 1];
342 for (u, nbrs) in adj.iter().enumerate() {
343 row_ptr[u + 1] = row_ptr[u] + nbrs.len();
344 }
345 let total_edges = row_ptr[n];
346 let mut col_idx = Vec::with_capacity(total_edges);
347 let mut wts = Vec::with_capacity(total_edges);
348 for nbrs in adj {
349 for &(v, w) in nbrs {
350 col_idx.push(v);
351 wts.push(w);
352 }
353 }
354
355 match config.backend {
356 GpuGraphBackend::CpuParallel => {
357 Ok(dijkstra_sequential(adj, source))
359 }
360 GpuGraphBackend::Gpu => {
361 #[cfg(feature = "wgpu")]
363 {
364 if total_edges >= GPU_EDGE_THRESHOLD {
365 match wgpu_delta_stepping(adj, source, delta) {
366 Ok(dist_f32) => {
367 return Ok(dist_f32.into_iter().map(|v| v as f64).collect());
368 }
369 Err(_) => {
370 }
372 }
373 }
374 }
375 Ok(dijkstra_sequential(adj, source))
376 }
377 }
378}
379
380fn dijkstra_sequential(adj: &[Vec<(usize, f64)>], src: usize) -> Vec<f64> {
382 let n = adj.len();
383 let mut dist = vec![f64::INFINITY; n];
384 dist[src] = 0.0;
385 let mut heap: BinaryHeap<(Reverse<u64>, usize)> = BinaryHeap::new();
386 heap.push((Reverse(0), src));
387 while let Some((Reverse(d_scaled), u)) = heap.pop() {
388 let d = d_scaled as f64 * 1e-9;
389 if d > dist[u] + 1e-12 {
390 continue;
391 }
392 for &(v, w) in &adj[u] {
393 let nd = dist[u] + w;
394 if nd < dist[v] - 1e-12 {
395 dist[v] = nd;
396 heap.push((Reverse((nd * 1e9) as u64), v));
397 }
398 }
399 }
400 dist
401}
402
403#[cfg(feature = "wgpu")]
408fn wgpu_bfs(
409 row_ptr: &[usize],
410 col_idx: &[usize],
411 source: usize,
412) -> std::result::Result<Vec<usize>, String> {
413 use wgpu::{
414 util::DeviceExt as _, Backends, BindGroupDescriptor, BindGroupEntry,
415 BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BufferBindingType,
416 BufferDescriptor, BufferUsages, CommandEncoderDescriptor, ComputePassDescriptor,
417 DeviceDescriptor, Features, Instance, InstanceDescriptor, Limits, PowerPreference,
418 RequestAdapterOptions, ShaderModuleDescriptor, ShaderSource, ShaderStages,
419 };
420
421 let n = row_ptr.len() - 1;
422
423 if n > u32::MAX as usize {
425 return Err("graph too large for u32 indexing".into());
426 }
427 if col_idx.len() > u32::MAX as usize {
428 return Err("edge list too large for u32 indexing".into());
429 }
430
431 let instance = Instance::new(InstanceDescriptor {
433 backends: Backends::all(),
434 flags: wgpu::InstanceFlags::default(),
435 memory_budget_thresholds: Default::default(),
436 backend_options: Default::default(),
437 display: None,
438 });
439 let adapter = pollster::block_on(instance.request_adapter(&RequestAdapterOptions {
440 power_preference: PowerPreference::HighPerformance,
441 compatible_surface: None,
442 force_fallback_adapter: false,
443 }))
444 .map_err(|_| "no wgpu adapter available".to_string())?;
445
446 let (device, queue) = pollster::block_on(adapter.request_device(&DeviceDescriptor {
447 label: Some("scirs2-graph-bfs"),
448 required_features: Features::empty(),
449 required_limits: Limits::default(),
450 ..Default::default()
451 }))
452 .map_err(|e| format!("device creation failed: {e}"))?;
453
454 let row_offsets_u32: Vec<u32> = row_ptr.iter().map(|&x| x as u32).collect();
456 let col_indices_u32: Vec<u32> = col_idx.iter().map(|&x| x as u32).collect();
457
458 let mut distances_i32: Vec<i32> = vec![-1i32; n];
460 distances_i32[source] = 0;
461
462 let frontier_capacity = n;
464
465 let buf_row = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
467 label: Some("bfs-row-offsets"),
468 contents: bytemuck::cast_slice(&row_offsets_u32),
469 usage: BufferUsages::STORAGE,
470 });
471 let buf_col = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
472 label: Some("bfs-col-indices"),
473 contents: bytemuck::cast_slice(&col_indices_u32),
474 usage: BufferUsages::STORAGE,
475 });
476 let buf_dist = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
477 label: Some("bfs-distances"),
478 contents: bytemuck::cast_slice(&distances_i32),
479 usage: BufferUsages::STORAGE | BufferUsages::COPY_SRC | BufferUsages::COPY_DST,
480 });
481
482 let frontier_bytes = (frontier_capacity * std::mem::size_of::<u32>()) as u64;
484 let buf_frontier_in = device.create_buffer(&BufferDescriptor {
485 label: Some("bfs-frontier-in"),
486 size: frontier_bytes,
487 usage: BufferUsages::STORAGE | BufferUsages::COPY_DST,
488 mapped_at_creation: false,
489 });
490 let buf_frontier_out = device.create_buffer(&BufferDescriptor {
491 label: Some("bfs-frontier-out"),
492 size: frontier_bytes,
493 usage: BufferUsages::STORAGE | BufferUsages::COPY_SRC | BufferUsages::COPY_DST,
494 mapped_at_creation: false,
495 });
496
497 let buf_frontier_count = device.create_buffer(&BufferDescriptor {
499 label: Some("bfs-frontier-count"),
500 size: 4,
501 usage: BufferUsages::STORAGE | BufferUsages::COPY_SRC | BufferUsages::COPY_DST,
502 mapped_at_creation: false,
503 });
504
505 let buf_uniforms = device.create_buffer(&BufferDescriptor {
508 label: Some("bfs-uniforms"),
509 size: 16,
510 usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
511 mapped_at_creation: false,
512 });
513
514 let dist_byte_len = (n * std::mem::size_of::<i32>()) as u64;
516 let buf_dist_staging = device.create_buffer(&BufferDescriptor {
517 label: Some("bfs-dist-staging"),
518 size: dist_byte_len,
519 usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST,
520 mapped_at_creation: false,
521 });
522 let buf_count_staging = device.create_buffer(&BufferDescriptor {
523 label: Some("bfs-count-staging"),
524 size: 4,
525 usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST,
526 mapped_at_creation: false,
527 });
528 let buf_frontier_out_staging = device.create_buffer(&BufferDescriptor {
529 label: Some("bfs-frontier-out-staging"),
530 size: frontier_bytes,
531 usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST,
532 mapped_at_creation: false,
533 });
534
535 use super::wgpu_shaders::BFS_FRONTIER_WGSL;
537 let shader_module = device.create_shader_module(ShaderModuleDescriptor {
538 label: Some("bfs-frontier-shader"),
539 source: ShaderSource::Wgsl(BFS_FRONTIER_WGSL.into()),
540 });
541
542 let bgl = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
544 label: Some("bfs-bgl"),
545 entries: &[
546 bgl_storage_ro(0), bgl_storage_ro(1), bgl_storage_rw(2), bgl_storage_ro(3), bgl_storage_rw(4), bgl_storage_rw(5), bgl_uniform(6), ],
554 });
555
556 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
557 label: Some("bfs-layout"),
558 bind_group_layouts: &[Some(&bgl)],
559 ..Default::default()
560 });
561
562 let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
563 label: Some("bfs-pipeline"),
564 layout: Some(&pipeline_layout),
565 module: &shader_module,
566 entry_point: Some("bfs_frontier"),
567 compilation_options: Default::default(),
568 cache: None,
569 });
570
571 let source_u32 = source as u32;
573 let source_bytes = source_u32.to_le_bytes();
574 queue.write_buffer(&buf_frontier_in, 0, &source_bytes);
575 queue.write_buffer(&buf_frontier_count, 0, &0u32.to_le_bytes());
576
577 let mut frontier_size: u32 = 1;
578 let mut current_level: i32 = 0;
579
580 while frontier_size > 0 {
582 let uniforms_data: [u8; 16] = {
584 let mut buf = [0u8; 16];
585 buf[0..4].copy_from_slice(&frontier_size.to_le_bytes());
586 buf[4..8].copy_from_slice(¤t_level.to_le_bytes());
587 buf
588 };
589 queue.write_buffer(&buf_uniforms, 0, &uniforms_data);
590 queue.write_buffer(&buf_frontier_count, 0, &0u32.to_le_bytes());
591
592 let zero_frontier: Vec<u8> = vec![0u8; frontier_capacity * 4];
594 queue.write_buffer(&buf_frontier_out, 0, &zero_frontier);
595
596 let bind_group = device.create_bind_group(&BindGroupDescriptor {
598 label: Some("bfs-bg"),
599 layout: &bgl,
600 entries: &[
601 BindGroupEntry {
602 binding: 0,
603 resource: buf_row.as_entire_binding(),
604 },
605 BindGroupEntry {
606 binding: 1,
607 resource: buf_col.as_entire_binding(),
608 },
609 BindGroupEntry {
610 binding: 2,
611 resource: buf_dist.as_entire_binding(),
612 },
613 BindGroupEntry {
614 binding: 3,
615 resource: buf_frontier_in.as_entire_binding(),
616 },
617 BindGroupEntry {
618 binding: 4,
619 resource: buf_frontier_out.as_entire_binding(),
620 },
621 BindGroupEntry {
622 binding: 5,
623 resource: buf_frontier_count.as_entire_binding(),
624 },
625 BindGroupEntry {
626 binding: 6,
627 resource: buf_uniforms.as_entire_binding(),
628 },
629 ],
630 });
631
632 let workgroups = frontier_size.div_ceil(64);
634 let mut encoder = device.create_command_encoder(&CommandEncoderDescriptor { label: None });
635 {
636 let mut pass = encoder.begin_compute_pass(&ComputePassDescriptor {
637 label: None,
638 timestamp_writes: None,
639 });
640 pass.set_pipeline(&pipeline);
641 pass.set_bind_group(0, &bind_group, &[]);
642 pass.dispatch_workgroups(workgroups, 1, 1);
643 }
644 encoder.copy_buffer_to_buffer(&buf_frontier_count, 0, &buf_count_staging, 0, 4);
646 encoder.copy_buffer_to_buffer(
648 &buf_frontier_out,
649 0,
650 &buf_frontier_out_staging,
651 0,
652 frontier_bytes,
653 );
654 queue.submit([encoder.finish()]);
655
656 device
658 .poll(wgpu::PollType::wait_indefinitely())
659 .map_err(|e| format!("GPU poll error: {e:?}"))?;
660
661 let next_count = read_u32_from_staging(&device, &buf_count_staging)?;
663 if next_count == 0 {
664 break;
665 }
666
667 let next_frontier =
669 read_u32_vec_from_staging(&device, &buf_frontier_out_staging, next_count as usize)?;
670
671 let next_bytes: Vec<u8> = next_frontier
673 .iter()
674 .flat_map(|&v| v.to_le_bytes())
675 .collect();
676 queue.write_buffer(&buf_frontier_in, 0, &next_bytes);
677
678 frontier_size = next_count;
679 current_level += 1;
680 }
681
682 let mut encoder = device.create_command_encoder(&CommandEncoderDescriptor { label: None });
684 encoder.copy_buffer_to_buffer(&buf_dist, 0, &buf_dist_staging, 0, dist_byte_len);
685 queue.submit([encoder.finish()]);
686
687 device
688 .poll(wgpu::PollType::wait_indefinitely())
689 .map_err(|e| format!("GPU poll error during dist readback: {e:?}"))?;
690
691 let dist_i32 = read_i32_vec_from_staging(&device, &buf_dist_staging, n)?;
692
693 let dist_usize: Vec<usize> = dist_i32
695 .into_iter()
696 .map(|d| if d < 0 { usize::MAX } else { d as usize })
697 .collect();
698
699 Ok(dist_usize)
700}
701
702#[cfg(feature = "wgpu")]
703fn wgpu_bellman_ford(
704 row_ptr: &[usize],
705 col_idx: &[usize],
706 weights: &[f32],
707 source: usize,
708 n: usize,
709) -> std::result::Result<Vec<f32>, String> {
710 use wgpu::{
711 util::DeviceExt as _, Backends, BindGroupDescriptor, BindGroupEntry,
712 BindGroupLayoutDescriptor, BufferDescriptor, BufferUsages, CommandEncoderDescriptor,
713 ComputePassDescriptor, DeviceDescriptor, Features, Instance, InstanceDescriptor, Limits,
714 PowerPreference, RequestAdapterOptions, ShaderModuleDescriptor, ShaderSource,
715 };
716
717 if n > u32::MAX as usize || col_idx.len() > u32::MAX as usize {
718 return Err("graph too large for u32 indexing".into());
719 }
720
721 let n_edges = col_idx.len();
723 let mut edge_src: Vec<u32> = Vec::with_capacity(n_edges);
724 let mut edge_dst: Vec<u32> = Vec::with_capacity(n_edges);
725 let mut edge_wt: Vec<f32> = Vec::with_capacity(n_edges);
726 for u in 0..n {
727 let start = row_ptr[u];
728 let end = row_ptr[u + 1];
729 for i in start..end {
730 edge_src.push(u as u32);
731 edge_dst.push(col_idx[i] as u32);
732 edge_wt.push(weights[i]);
733 }
734 }
735
736 let instance = Instance::new(InstanceDescriptor {
738 backends: Backends::all(),
739 flags: wgpu::InstanceFlags::default(),
740 memory_budget_thresholds: Default::default(),
741 backend_options: Default::default(),
742 display: None,
743 });
744 let adapter = pollster::block_on(instance.request_adapter(&RequestAdapterOptions {
745 power_preference: PowerPreference::HighPerformance,
746 compatible_surface: None,
747 force_fallback_adapter: false,
748 }))
749 .map_err(|_| "no wgpu adapter available".to_string())?;
750
751 let (device, queue) = pollster::block_on(adapter.request_device(&DeviceDescriptor {
752 label: Some("scirs2-graph-bf"),
753 required_features: Features::empty(),
754 required_limits: Limits::default(),
755 ..Default::default()
756 }))
757 .map_err(|e| format!("device creation failed: {e}"))?;
758
759 let inf_bits = f32::INFINITY.to_bits();
761 let mut dist_bits: Vec<u32> = vec![inf_bits; n];
762 dist_bits[source] = 0u32;
763
764 let buf_src = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
766 label: Some("bf-edge-src"),
767 contents: bytemuck::cast_slice(&edge_src),
768 usage: BufferUsages::STORAGE,
769 });
770 let buf_dst = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
771 label: Some("bf-edge-dst"),
772 contents: bytemuck::cast_slice(&edge_dst),
773 usage: BufferUsages::STORAGE,
774 });
775 let buf_wt = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
776 label: Some("bf-edge-wt"),
777 contents: bytemuck::cast_slice(&edge_wt),
778 usage: BufferUsages::STORAGE,
779 });
780 let buf_dist = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
781 label: Some("bf-distances"),
782 contents: bytemuck::cast_slice(&dist_bits),
783 usage: BufferUsages::STORAGE | BufferUsages::COPY_SRC | BufferUsages::COPY_DST,
784 });
785
786 let uniforms: [u32; 4] = [n_edges as u32, 0, 0, 0];
788 let buf_uniforms = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
789 label: Some("bf-uniforms"),
790 contents: bytemuck::cast_slice(&uniforms),
791 usage: BufferUsages::UNIFORM,
792 });
793
794 use super::wgpu_shaders::SSSP_BELLMAN_FORD_WGSL;
796 let shader_module = device.create_shader_module(ShaderModuleDescriptor {
797 label: Some("bf-shader"),
798 source: ShaderSource::Wgsl(SSSP_BELLMAN_FORD_WGSL.into()),
799 });
800
801 let bgl = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
802 label: Some("bf-bgl"),
803 entries: &[
804 bgl_storage_ro(0), bgl_storage_ro(1), bgl_storage_ro(2), bgl_storage_rw(3), bgl_uniform(4), ],
810 });
811
812 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
813 label: Some("bf-layout"),
814 bind_group_layouts: &[Some(&bgl)],
815 ..Default::default()
816 });
817
818 let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
819 label: Some("bf-pipeline"),
820 layout: Some(&pipeline_layout),
821 module: &shader_module,
822 entry_point: Some("sssp_bellman_ford"),
823 compilation_options: Default::default(),
824 cache: None,
825 });
826
827 let bind_group = device.create_bind_group(&BindGroupDescriptor {
828 label: Some("bf-bg"),
829 layout: &bgl,
830 entries: &[
831 BindGroupEntry {
832 binding: 0,
833 resource: buf_src.as_entire_binding(),
834 },
835 BindGroupEntry {
836 binding: 1,
837 resource: buf_dst.as_entire_binding(),
838 },
839 BindGroupEntry {
840 binding: 2,
841 resource: buf_wt.as_entire_binding(),
842 },
843 BindGroupEntry {
844 binding: 3,
845 resource: buf_dist.as_entire_binding(),
846 },
847 BindGroupEntry {
848 binding: 4,
849 resource: buf_uniforms.as_entire_binding(),
850 },
851 ],
852 });
853
854 let workgroups = (n_edges as u32).div_ceil(64);
855
856 for _ in 0..(n.saturating_sub(1)) {
858 let mut encoder = device.create_command_encoder(&CommandEncoderDescriptor { label: None });
859 {
860 let mut pass = encoder.begin_compute_pass(&ComputePassDescriptor {
861 label: None,
862 timestamp_writes: None,
863 });
864 pass.set_pipeline(&pipeline);
865 pass.set_bind_group(0, &bind_group, &[]);
866 pass.dispatch_workgroups(workgroups, 1, 1);
867 }
868 queue.submit([encoder.finish()]);
869
870 device
871 .poll(wgpu::PollType::wait_indefinitely())
872 .map_err(|e| format!("GPU poll error: {e:?}"))?;
873 }
874
875 let dist_byte_len = (n * std::mem::size_of::<u32>()) as u64;
877 let buf_staging = device.create_buffer(&BufferDescriptor {
878 label: Some("bf-staging"),
879 size: dist_byte_len,
880 usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST,
881 mapped_at_creation: false,
882 });
883 let mut encoder = device.create_command_encoder(&CommandEncoderDescriptor { label: None });
884 encoder.copy_buffer_to_buffer(&buf_dist, 0, &buf_staging, 0, dist_byte_len);
885 queue.submit([encoder.finish()]);
886
887 device
888 .poll(wgpu::PollType::wait_indefinitely())
889 .map_err(|e| format!("GPU poll error during readback: {e:?}"))?;
890
891 let result_bits = read_u32_vec_from_staging(&device, &buf_staging, n)?;
892 Ok(result_bits.into_iter().map(f32::from_bits).collect())
893}
894
895#[cfg(feature = "wgpu")]
900fn wgpu_delta_stepping(
901 adj: &[Vec<(usize, f64)>],
902 source: usize,
903 delta: f64,
904) -> std::result::Result<Vec<f32>, String> {
905 use super::wgpu_shaders::{DELTA_APPLY_WGSL, DELTA_HEAVY_WGSL, DELTA_LIGHT_WGSL};
906 use wgpu::{
907 util::DeviceExt as _, Backends, BindGroupDescriptor, BindGroupEntry,
908 BindGroupLayoutDescriptor, BufferDescriptor, BufferUsages, CommandEncoderDescriptor,
909 ComputePassDescriptor, DeviceDescriptor, Features, Instance, InstanceDescriptor, Limits,
910 PowerPreference, RequestAdapterOptions, ShaderModuleDescriptor, ShaderSource,
911 };
912
913 let n = adj.len();
914 if n > u32::MAX as usize {
915 return Err("graph too large for u32 indexing".into());
916 }
917
918 let delta_f32 = delta as f32;
920 let mut light_src: Vec<u32> = Vec::new();
921 let mut light_dst: Vec<u32> = Vec::new();
922 let mut light_wt: Vec<f32> = Vec::new();
923 let mut heavy_src: Vec<u32> = Vec::new();
924 let mut heavy_dst: Vec<u32> = Vec::new();
925 let mut heavy_wt: Vec<f32> = Vec::new();
926 for (u, nbrs) in adj.iter().enumerate() {
927 for &(v, w) in nbrs {
928 let w32 = w as f32;
929 if w32 <= delta_f32 {
930 light_src.push(u as u32);
931 light_dst.push(v as u32);
932 light_wt.push(w32);
933 } else {
934 heavy_src.push(u as u32);
935 heavy_dst.push(v as u32);
936 heavy_wt.push(w32);
937 }
938 }
939 }
940
941 let instance = Instance::new(InstanceDescriptor {
943 backends: Backends::all(),
944 flags: wgpu::InstanceFlags::default(),
945 memory_budget_thresholds: Default::default(),
946 backend_options: Default::default(),
947 display: None,
948 });
949 let adapter = pollster::block_on(instance.request_adapter(&RequestAdapterOptions {
950 power_preference: PowerPreference::HighPerformance,
951 compatible_surface: None,
952 force_fallback_adapter: false,
953 }))
954 .map_err(|_| "no wgpu adapter available".to_string())?;
955
956 let (device, queue) = pollster::block_on(adapter.request_device(&DeviceDescriptor {
957 label: Some("scirs2-graph-delta"),
958 required_features: Features::empty(),
959 required_limits: Limits::default(),
960 ..Default::default()
961 }))
962 .map_err(|e| format!("device creation failed: {e}"))?;
963
964 let inf_bits = f32::INFINITY.to_bits();
966 let mut dist_bits: Vec<u32> = vec![inf_bits; n];
967 dist_bits[source] = 0u32; let buf_dist = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
971 label: Some("ds-distances"),
972 contents: bytemuck::cast_slice(&dist_bits),
973 usage: BufferUsages::STORAGE | BufferUsages::COPY_SRC | BufferUsages::COPY_DST,
974 });
975
976 let proposed_init: Vec<u32> = vec![inf_bits; n];
978 let buf_proposed = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
979 label: Some("ds-proposed"),
980 contents: bytemuck::cast_slice(&proposed_init),
981 usage: BufferUsages::STORAGE | BufferUsages::COPY_DST,
982 });
983
984 let buf_changed = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
986 label: Some("ds-changed"),
987 contents: bytemuck::cast_slice(&[0u32]),
988 usage: BufferUsages::STORAGE | BufferUsages::COPY_SRC | BufferUsages::COPY_DST,
989 });
990 let buf_changed_staging = device.create_buffer(&BufferDescriptor {
991 label: Some("ds-changed-staging"),
992 size: 4,
993 usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST,
994 mapped_at_creation: false,
995 });
996
997 let n_light = light_src.len();
999 let (buf_light_src, buf_light_dst, buf_light_wt) = if n_light > 0 {
1001 let s = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1002 label: Some("ds-light-src"),
1003 contents: bytemuck::cast_slice(&light_src),
1004 usage: BufferUsages::STORAGE,
1005 });
1006 let d = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1007 label: Some("ds-light-dst"),
1008 contents: bytemuck::cast_slice(&light_dst),
1009 usage: BufferUsages::STORAGE,
1010 });
1011 let w = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1012 label: Some("ds-light-wt"),
1013 contents: bytemuck::cast_slice(&light_wt),
1014 usage: BufferUsages::STORAGE,
1015 });
1016 (s, d, w)
1017 } else {
1018 let placeholder: [u32; 1] = [0];
1020 let s = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1021 label: Some("ds-light-src-ph"),
1022 contents: bytemuck::cast_slice(&placeholder),
1023 usage: BufferUsages::STORAGE,
1024 });
1025 let d = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1026 label: Some("ds-light-dst-ph"),
1027 contents: bytemuck::cast_slice(&placeholder),
1028 usage: BufferUsages::STORAGE,
1029 });
1030 let w_ph: [f32; 1] = [0.0];
1031 let w = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1032 label: Some("ds-light-wt-ph"),
1033 contents: bytemuck::cast_slice(&w_ph),
1034 usage: BufferUsages::STORAGE,
1035 });
1036 (s, d, w)
1037 };
1038
1039 let n_heavy = heavy_src.len();
1041 let (buf_heavy_src, buf_heavy_dst, buf_heavy_wt) = if n_heavy > 0 {
1042 let s = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1043 label: Some("ds-heavy-src"),
1044 contents: bytemuck::cast_slice(&heavy_src),
1045 usage: BufferUsages::STORAGE,
1046 });
1047 let d = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1048 label: Some("ds-heavy-dst"),
1049 contents: bytemuck::cast_slice(&heavy_dst),
1050 usage: BufferUsages::STORAGE,
1051 });
1052 let w = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1053 label: Some("ds-heavy-wt"),
1054 contents: bytemuck::cast_slice(&heavy_wt),
1055 usage: BufferUsages::STORAGE,
1056 });
1057 (s, d, w)
1058 } else {
1059 let placeholder: [u32; 1] = [0];
1060 let s = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1061 label: Some("ds-heavy-src-ph"),
1062 contents: bytemuck::cast_slice(&placeholder),
1063 usage: BufferUsages::STORAGE,
1064 });
1065 let d = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1066 label: Some("ds-heavy-dst-ph"),
1067 contents: bytemuck::cast_slice(&placeholder),
1068 usage: BufferUsages::STORAGE,
1069 });
1070 let w_ph: [f32; 1] = [0.0];
1071 let w = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1072 label: Some("ds-heavy-wt-ph"),
1073 contents: bytemuck::cast_slice(&w_ph),
1074 usage: BufferUsages::STORAGE,
1075 });
1076 (s, d, w)
1077 };
1078
1079 let light_shader = device.create_shader_module(ShaderModuleDescriptor {
1081 label: Some("ds-light-shader"),
1082 source: ShaderSource::Wgsl(DELTA_LIGHT_WGSL.into()),
1083 });
1084 let apply_shader = device.create_shader_module(ShaderModuleDescriptor {
1085 label: Some("ds-apply-shader"),
1086 source: ShaderSource::Wgsl(DELTA_APPLY_WGSL.into()),
1087 });
1088 let heavy_shader = device.create_shader_module(ShaderModuleDescriptor {
1089 label: Some("ds-heavy-shader"),
1090 source: ShaderSource::Wgsl(DELTA_HEAVY_WGSL.into()),
1091 });
1092
1093 let light_bgl = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
1096 label: Some("ds-light-bgl"),
1097 entries: &[
1098 bgl_storage_ro(0), bgl_storage_ro(1), bgl_storage_ro(2), bgl_storage_ro(3), bgl_storage_rw(4), bgl_uniform(5), ],
1105 });
1106
1107 let apply_bgl = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
1109 label: Some("ds-apply-bgl"),
1110 entries: &[
1111 bgl_storage_ro(0), bgl_storage_rw(1), bgl_storage_rw(2), bgl_uniform(3), ],
1116 });
1117
1118 let heavy_bgl = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
1120 label: Some("ds-heavy-bgl"),
1121 entries: &[
1122 bgl_storage_ro(0), bgl_storage_ro(1), bgl_storage_ro(2), bgl_storage_rw(3), bgl_storage_rw(4), bgl_uniform(5), ],
1129 });
1130
1131 let light_pl_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1133 label: Some("ds-light-layout"),
1134 bind_group_layouts: &[Some(&light_bgl)],
1135 ..Default::default()
1136 });
1137 let apply_pl_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1138 label: Some("ds-apply-layout"),
1139 bind_group_layouts: &[Some(&apply_bgl)],
1140 ..Default::default()
1141 });
1142 let heavy_pl_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1143 label: Some("ds-heavy-layout"),
1144 bind_group_layouts: &[Some(&heavy_bgl)],
1145 ..Default::default()
1146 });
1147
1148 let light_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
1149 label: Some("ds-light-pipeline"),
1150 layout: Some(&light_pl_layout),
1151 module: &light_shader,
1152 entry_point: Some("delta_light"),
1153 compilation_options: Default::default(),
1154 cache: None,
1155 });
1156 let apply_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
1157 label: Some("ds-apply-pipeline"),
1158 layout: Some(&apply_pl_layout),
1159 module: &apply_shader,
1160 entry_point: Some("delta_apply"),
1161 compilation_options: Default::default(),
1162 cache: None,
1163 });
1164 let heavy_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
1165 label: Some("ds-heavy-pipeline"),
1166 layout: Some(&heavy_pl_layout),
1167 module: &heavy_shader,
1168 entry_point: Some("delta_heavy"),
1169 compilation_options: Default::default(),
1170 cache: None,
1171 });
1172
1173 let light_uniforms: [u32; 4] = [n_light as u32, 0, 0, 0];
1175 let buf_light_uniforms = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1176 label: Some("ds-light-uniforms"),
1177 contents: bytemuck::cast_slice(&light_uniforms),
1178 usage: BufferUsages::UNIFORM,
1179 });
1180
1181 let apply_uniforms: [u32; 4] = [n as u32, 0, 0, 0];
1182 let buf_apply_uniforms = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1183 label: Some("ds-apply-uniforms"),
1184 contents: bytemuck::cast_slice(&apply_uniforms),
1185 usage: BufferUsages::UNIFORM,
1186 });
1187
1188 let heavy_uniforms: [u32; 4] = [n_heavy as u32, 0, 0, 0];
1189 let buf_heavy_uniforms = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1190 label: Some("ds-heavy-uniforms"),
1191 contents: bytemuck::cast_slice(&heavy_uniforms),
1192 usage: BufferUsages::UNIFORM,
1193 });
1194
1195 let light_bg = device.create_bind_group(&BindGroupDescriptor {
1197 label: Some("ds-light-bg"),
1198 layout: &light_bgl,
1199 entries: &[
1200 BindGroupEntry {
1201 binding: 0,
1202 resource: buf_light_src.as_entire_binding(),
1203 },
1204 BindGroupEntry {
1205 binding: 1,
1206 resource: buf_light_dst.as_entire_binding(),
1207 },
1208 BindGroupEntry {
1209 binding: 2,
1210 resource: buf_light_wt.as_entire_binding(),
1211 },
1212 BindGroupEntry {
1213 binding: 3,
1214 resource: buf_dist.as_entire_binding(),
1215 },
1216 BindGroupEntry {
1217 binding: 4,
1218 resource: buf_proposed.as_entire_binding(),
1219 },
1220 BindGroupEntry {
1221 binding: 5,
1222 resource: buf_light_uniforms.as_entire_binding(),
1223 },
1224 ],
1225 });
1226
1227 let apply_bg = device.create_bind_group(&BindGroupDescriptor {
1228 label: Some("ds-apply-bg"),
1229 layout: &apply_bgl,
1230 entries: &[
1231 BindGroupEntry {
1232 binding: 0,
1233 resource: buf_proposed.as_entire_binding(),
1234 },
1235 BindGroupEntry {
1236 binding: 1,
1237 resource: buf_dist.as_entire_binding(),
1238 },
1239 BindGroupEntry {
1240 binding: 2,
1241 resource: buf_changed.as_entire_binding(),
1242 },
1243 BindGroupEntry {
1244 binding: 3,
1245 resource: buf_apply_uniforms.as_entire_binding(),
1246 },
1247 ],
1248 });
1249
1250 let heavy_bg = device.create_bind_group(&BindGroupDescriptor {
1251 label: Some("ds-heavy-bg"),
1252 layout: &heavy_bgl,
1253 entries: &[
1254 BindGroupEntry {
1255 binding: 0,
1256 resource: buf_heavy_src.as_entire_binding(),
1257 },
1258 BindGroupEntry {
1259 binding: 1,
1260 resource: buf_heavy_dst.as_entire_binding(),
1261 },
1262 BindGroupEntry {
1263 binding: 2,
1264 resource: buf_heavy_wt.as_entire_binding(),
1265 },
1266 BindGroupEntry {
1267 binding: 3,
1268 resource: buf_dist.as_entire_binding(),
1269 },
1270 BindGroupEntry {
1271 binding: 4,
1272 resource: buf_changed.as_entire_binding(),
1273 },
1274 BindGroupEntry {
1275 binding: 5,
1276 resource: buf_heavy_uniforms.as_entire_binding(),
1277 },
1278 ],
1279 });
1280
1281 let light_wg = (n_light as u32).saturating_add(255) / 256;
1283 let apply_wg = (n as u32).saturating_add(255) / 256;
1284 let heavy_wg = (n_heavy as u32).saturating_add(255) / 256;
1285
1286 let max_iters = n + 1;
1289 for _ in 0..max_iters {
1290 let inf_reset: Vec<u32> = vec![inf_bits; n];
1292 queue.write_buffer(&buf_proposed, 0, bytemuck::cast_slice(&inf_reset));
1293 queue.write_buffer(&buf_changed, 0, &0u32.to_le_bytes());
1295
1296 if n_light > 0 {
1298 let mut encoder =
1299 device.create_command_encoder(&CommandEncoderDescriptor { label: None });
1300 {
1301 let mut pass = encoder.begin_compute_pass(&ComputePassDescriptor {
1302 label: None,
1303 timestamp_writes: None,
1304 });
1305 pass.set_pipeline(&light_pipeline);
1306 pass.set_bind_group(0, &light_bg, &[]);
1307 pass.dispatch_workgroups(light_wg.max(1), 1, 1);
1308 }
1309 queue.submit([encoder.finish()]);
1310 device
1311 .poll(wgpu::PollType::wait_indefinitely())
1312 .map_err(|e| format!("GPU poll (light): {e:?}"))?;
1313 }
1314
1315 {
1317 let mut encoder =
1318 device.create_command_encoder(&CommandEncoderDescriptor { label: None });
1319 {
1320 let mut pass = encoder.begin_compute_pass(&ComputePassDescriptor {
1321 label: None,
1322 timestamp_writes: None,
1323 });
1324 pass.set_pipeline(&apply_pipeline);
1325 pass.set_bind_group(0, &apply_bg, &[]);
1326 pass.dispatch_workgroups(apply_wg.max(1), 1, 1);
1327 }
1328 queue.submit([encoder.finish()]);
1329 device
1330 .poll(wgpu::PollType::wait_indefinitely())
1331 .map_err(|e| format!("GPU poll (apply): {e:?}"))?;
1332 }
1333
1334 if n_heavy > 0 {
1336 let mut encoder =
1337 device.create_command_encoder(&CommandEncoderDescriptor { label: None });
1338 {
1339 let mut pass = encoder.begin_compute_pass(&ComputePassDescriptor {
1340 label: None,
1341 timestamp_writes: None,
1342 });
1343 pass.set_pipeline(&heavy_pipeline);
1344 pass.set_bind_group(0, &heavy_bg, &[]);
1345 pass.dispatch_workgroups(heavy_wg.max(1), 1, 1);
1346 }
1347 encoder.copy_buffer_to_buffer(&buf_changed, 0, &buf_changed_staging, 0, 4);
1349 queue.submit([encoder.finish()]);
1350 device
1351 .poll(wgpu::PollType::wait_indefinitely())
1352 .map_err(|e| format!("GPU poll (heavy): {e:?}"))?;
1353 } else {
1354 let mut encoder =
1356 device.create_command_encoder(&CommandEncoderDescriptor { label: None });
1357 encoder.copy_buffer_to_buffer(&buf_changed, 0, &buf_changed_staging, 0, 4);
1358 queue.submit([encoder.finish()]);
1359 device
1360 .poll(wgpu::PollType::wait_indefinitely())
1361 .map_err(|e| format!("GPU poll (no-heavy): {e:?}"))?;
1362 }
1363
1364 let changed = read_u32_from_staging(&device, &buf_changed_staging)?;
1366 if changed == 0 {
1367 break;
1368 }
1369 }
1370
1371 let dist_byte_len = (n * std::mem::size_of::<u32>()) as u64;
1373 let buf_staging = device.create_buffer(&BufferDescriptor {
1374 label: Some("ds-dist-staging"),
1375 size: dist_byte_len,
1376 usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST,
1377 mapped_at_creation: false,
1378 });
1379 let mut encoder = device.create_command_encoder(&CommandEncoderDescriptor { label: None });
1380 encoder.copy_buffer_to_buffer(&buf_dist, 0, &buf_staging, 0, dist_byte_len);
1381 queue.submit([encoder.finish()]);
1382 device
1383 .poll(wgpu::PollType::wait_indefinitely())
1384 .map_err(|e| format!("GPU poll (readback): {e:?}"))?;
1385
1386 let result_bits = read_u32_vec_from_staging(&device, &buf_staging, n)?;
1387 Ok(result_bits.into_iter().map(f32::from_bits).collect())
1388}
1389
1390#[cfg(feature = "wgpu")]
1395fn bgl_storage_ro(binding: u32) -> wgpu::BindGroupLayoutEntry {
1396 wgpu::BindGroupLayoutEntry {
1397 binding,
1398 visibility: wgpu::ShaderStages::COMPUTE,
1399 ty: wgpu::BindingType::Buffer {
1400 ty: wgpu::BufferBindingType::Storage { read_only: true },
1401 has_dynamic_offset: false,
1402 min_binding_size: None,
1403 },
1404 count: None,
1405 }
1406}
1407
1408#[cfg(feature = "wgpu")]
1409fn bgl_storage_rw(binding: u32) -> wgpu::BindGroupLayoutEntry {
1410 wgpu::BindGroupLayoutEntry {
1411 binding,
1412 visibility: wgpu::ShaderStages::COMPUTE,
1413 ty: wgpu::BindingType::Buffer {
1414 ty: wgpu::BufferBindingType::Storage { read_only: false },
1415 has_dynamic_offset: false,
1416 min_binding_size: None,
1417 },
1418 count: None,
1419 }
1420}
1421
1422#[cfg(feature = "wgpu")]
1423fn bgl_uniform(binding: u32) -> wgpu::BindGroupLayoutEntry {
1424 wgpu::BindGroupLayoutEntry {
1425 binding,
1426 visibility: wgpu::ShaderStages::COMPUTE,
1427 ty: wgpu::BindingType::Buffer {
1428 ty: wgpu::BufferBindingType::Uniform,
1429 has_dynamic_offset: false,
1430 min_binding_size: None,
1431 },
1432 count: None,
1433 }
1434}
1435
1436#[cfg(feature = "wgpu")]
1441fn read_u32_from_staging(
1442 device: &wgpu::Device,
1443 staging: &wgpu::Buffer,
1444) -> std::result::Result<u32, String> {
1445 let slice = staging.slice(0..4);
1446 let (tx, rx) = std::sync::mpsc::channel();
1447 slice.map_async(wgpu::MapMode::Read, move |r| {
1448 let _ = tx.send(r);
1449 });
1450 device
1451 .poll(wgpu::PollType::wait_indefinitely())
1452 .map_err(|e| format!("poll error: {e:?}"))?;
1453 rx.recv()
1454 .map_err(|_| "channel closed".to_string())?
1455 .map_err(|e| format!("map_async failed: {e:?}"))?;
1456 let mapped = slice.get_mapped_range();
1457 let val = u32::from_le_bytes([mapped[0], mapped[1], mapped[2], mapped[3]]);
1458 drop(mapped);
1459 staging.unmap();
1460 Ok(val)
1461}
1462
1463#[cfg(feature = "wgpu")]
1464fn read_u32_vec_from_staging(
1465 device: &wgpu::Device,
1466 staging: &wgpu::Buffer,
1467 count: usize,
1468) -> std::result::Result<Vec<u32>, String> {
1469 let byte_len = (count * 4) as u64;
1470 let slice = staging.slice(0..byte_len);
1471 let (tx, rx) = std::sync::mpsc::channel();
1472 slice.map_async(wgpu::MapMode::Read, move |r| {
1473 let _ = tx.send(r);
1474 });
1475 device
1476 .poll(wgpu::PollType::wait_indefinitely())
1477 .map_err(|e| format!("poll error: {e:?}"))?;
1478 rx.recv()
1479 .map_err(|_| "channel closed".to_string())?
1480 .map_err(|e| format!("map_async failed: {e:?}"))?;
1481 let mapped = slice.get_mapped_range();
1482 let result = mapped
1483 .chunks_exact(4)
1484 .map(|c| u32::from_le_bytes([c[0], c[1], c[2], c[3]]))
1485 .collect();
1486 drop(mapped);
1487 staging.unmap();
1488 Ok(result)
1489}
1490
1491#[cfg(feature = "wgpu")]
1492fn read_i32_vec_from_staging(
1493 device: &wgpu::Device,
1494 staging: &wgpu::Buffer,
1495 count: usize,
1496) -> std::result::Result<Vec<i32>, String> {
1497 let byte_len = (count * 4) as u64;
1498 let slice = staging.slice(0..byte_len);
1499 let (tx, rx) = std::sync::mpsc::channel();
1500 slice.map_async(wgpu::MapMode::Read, move |r| {
1501 let _ = tx.send(r);
1502 });
1503 device
1504 .poll(wgpu::PollType::wait_indefinitely())
1505 .map_err(|e| format!("poll error: {e:?}"))?;
1506 rx.recv()
1507 .map_err(|_| "channel closed".to_string())?
1508 .map_err(|e| format!("map_async failed: {e:?}"))?;
1509 let mapped = slice.get_mapped_range();
1510 let result = mapped
1511 .chunks_exact(4)
1512 .map(|c| i32::from_le_bytes([c[0], c[1], c[2], c[3]]))
1513 .collect();
1514 drop(mapped);
1515 staging.unmap();
1516 Ok(result)
1517}
1518
1519#[cfg(test)]
1524mod tests {
1525 use super::*;
1526
1527 fn build_csr(n: usize, edges: &[(usize, usize)]) -> (Vec<usize>, Vec<usize>) {
1528 let mut adj: Vec<Vec<usize>> = vec![vec![]; n];
1529 for &(u, v) in edges {
1530 adj[u].push(v);
1531 adj[v].push(u);
1532 }
1533 let mut row_ptr = vec![0usize; n + 1];
1534 for i in 0..n {
1535 row_ptr[i + 1] = row_ptr[i] + adj[i].len();
1536 }
1537 let col_idx: Vec<usize> = adj.into_iter().flatten().collect();
1538 (row_ptr, col_idx)
1539 }
1540
1541 fn build_csr_directed(
1542 n: usize,
1543 edges: &[(usize, usize, f64)],
1544 ) -> (Vec<usize>, Vec<usize>, Vec<f64>) {
1545 let mut adj: Vec<Vec<(usize, f64)>> = vec![vec![]; n];
1546 for &(u, v, w) in edges {
1547 adj[u].push((v, w));
1548 }
1549 let mut row_ptr = vec![0usize; n + 1];
1550 for i in 0..n {
1551 row_ptr[i + 1] = row_ptr[i] + adj[i].len();
1552 }
1553 let mut col_idx = Vec::new();
1554 let mut weights = Vec::new();
1555 for nbrs in adj {
1556 for (v, w) in nbrs {
1557 col_idx.push(v);
1558 weights.push(w);
1559 }
1560 }
1561 (row_ptr, col_idx, weights)
1562 }
1563
1564 fn dijkstra_ref(adj: &[Vec<(usize, f64)>], src: usize) -> Vec<f64> {
1565 let n = adj.len();
1566 let mut dist = vec![f64::INFINITY; n];
1567 dist[src] = 0.0;
1568 let mut heap: BinaryHeap<(Reverse<u64>, usize)> = BinaryHeap::new();
1569 heap.push((Reverse(0), src));
1570 while let Some((Reverse(d), u)) = heap.pop() {
1571 let d = d as f64 * 1e-9;
1572 if d > dist[u] + 1e-12 {
1573 continue;
1574 }
1575 for &(v, w) in &adj[u] {
1576 let nd = dist[u] + w;
1577 if nd < dist[v] - 1e-12 {
1578 dist[v] = nd;
1579 heap.push((Reverse((nd * 1e9) as u64), v));
1580 }
1581 }
1582 }
1583 dist
1584 }
1585
1586 #[test]
1587 fn test_gpu_bfs_path_graph() {
1588 let (rp, ci) = build_csr(5, &[(0, 1), (1, 2), (2, 3), (3, 4)]);
1589 let dist = gpu_bfs(&rp, &ci, 0, &GpuBfsConfig::default()).expect("bfs failed");
1590 assert_eq!(dist, vec![0, 1, 2, 3, 4]);
1591 }
1592
1593 #[test]
1594 fn test_gpu_bfs_connected() {
1595 let edges: Vec<(usize, usize)> = (0..5usize)
1596 .flat_map(|i| ((i + 1)..5).map(move |j| (i, j)))
1597 .collect();
1598 let (rp, ci) = build_csr(5, &edges);
1599 let dist = gpu_bfs(&rp, &ci, 0, &GpuBfsConfig::default()).expect("bfs failed");
1600 assert_eq!(dist[0], 0);
1601 for i in 1..5 {
1602 assert_eq!(dist[i], 1);
1603 }
1604 }
1605
1606 #[test]
1607 fn test_gpu_bfs_disconnected() {
1608 let (rp, ci) = build_csr(4, &[(0, 1), (2, 3)]);
1609 let dist = gpu_bfs(&rp, &ci, 0, &GpuBfsConfig::default()).expect("bfs failed");
1610 assert_eq!(dist[0], 0);
1611 assert_eq!(dist[1], 1);
1612 assert_eq!(dist[2], usize::MAX);
1613 assert_eq!(dist[3], usize::MAX);
1614 }
1615
1616 #[test]
1617 fn test_gpu_bfs_single_node() {
1618 let rp = vec![0usize, 0];
1619 let ci: Vec<usize> = vec![];
1620 let dist = gpu_bfs(&rp, &ci, 0, &GpuBfsConfig::default()).expect("bfs failed");
1621 assert_eq!(dist, vec![0]);
1622 }
1623
1624 #[test]
1625 fn test_gpu_bfs_invalid_source() {
1626 let (rp, ci) = build_csr(4, &[(0, 1)]);
1627 assert!(gpu_bfs(&rp, &ci, 10, &GpuBfsConfig::default()).is_err());
1628 }
1629
1630 #[test]
1631 fn test_gpu_bfs_tree() {
1632 let (rp, ci) = build_csr(7, &[(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)]);
1633 let dist = gpu_bfs(&rp, &ci, 0, &GpuBfsConfig::default()).expect("bfs failed");
1634 assert_eq!(dist, vec![0, 1, 1, 2, 2, 2, 2]);
1635 }
1636
1637 #[test]
1638 fn test_gpu_bfs_star_graph() {
1639 let edges: Vec<(usize, usize)> = (1..=5).map(|i| (0, i)).collect();
1640 let (rp, ci) = build_csr(6, &edges);
1641 let dist = gpu_bfs(&rp, &ci, 0, &GpuBfsConfig::default()).expect("bfs failed");
1642 assert_eq!(dist[0], 0);
1643 for i in 1..=5 {
1644 assert_eq!(dist[i], 1);
1645 }
1646 }
1647
1648 #[test]
1649 fn test_gpu_bfs_cycle() {
1650 let (rp, ci) = build_csr(4, &[(0, 1), (1, 2), (2, 3), (3, 0)]);
1651 let dist = gpu_bfs(&rp, &ci, 0, &GpuBfsConfig::default()).expect("bfs failed");
1652 assert_eq!(dist[0], 0);
1653 assert_eq!(dist[1], 1);
1654 assert_eq!(dist[2], 2);
1655 assert_eq!(dist[3], 1);
1657 }
1658
1659 #[test]
1660 fn test_gpu_sssp_shortest_paths() {
1661 let (rp, ci, w) = build_csr_directed(3, &[(0, 1, 1.0), (0, 2, 4.0), (1, 2, 2.0)]);
1663 let dist =
1664 gpu_sssp_bellman_ford(&rp, &ci, &w, 0, &GpuBfsConfig::default()).expect("sssp failed");
1665 assert!((dist[0] - 0.0).abs() < 1e-10);
1666 assert!((dist[1] - 1.0).abs() < 1e-10);
1667 assert!(
1668 (dist[2] - 3.0).abs() < 1e-10,
1669 "expected 3.0, got {}",
1670 dist[2]
1671 );
1672 }
1673
1674 #[test]
1675 fn test_gpu_sssp_negative_weight_detection() {
1676 let (rp, ci, w) = build_csr_directed(3, &[(0, 1, 1.0), (1, 0, -2.0), (0, 2, 5.0)]);
1678 assert!(gpu_sssp_bellman_ford(&rp, &ci, &w, 0, &GpuBfsConfig::default()).is_err());
1679 }
1680
1681 #[test]
1682 fn test_gpu_sssp_unreachable() {
1683 let (rp, ci, w) = build_csr_directed(3, &[(0, 1, 1.0)]);
1684 let dist =
1685 gpu_sssp_bellman_ford(&rp, &ci, &w, 0, &GpuBfsConfig::default()).expect("sssp failed");
1686 assert_eq!(dist[2], f64::INFINITY);
1687 }
1688
1689 #[test]
1690 fn test_gpu_sssp_path_graph() {
1691 let (rp, ci, w) = build_csr_directed(4, &[(0, 1, 1.0), (1, 2, 1.0), (2, 3, 1.0)]);
1692 let dist =
1693 gpu_sssp_bellman_ford(&rp, &ci, &w, 0, &GpuBfsConfig::default()).expect("sssp failed");
1694 for i in 0..4usize {
1695 assert!(
1696 (dist[i] - i as f64).abs() < 1e-10,
1697 "dist[{}]={}",
1698 i,
1699 dist[i]
1700 );
1701 }
1702 }
1703
1704 #[test]
1705 fn test_delta_stepping_matches_dijkstra() {
1706 let adj = vec![
1707 vec![(1usize, 2.0f64), (2, 6.0)],
1708 vec![(3usize, 1.0f64), (2, 3.0)],
1709 vec![(4usize, 1.0f64)],
1710 vec![(4usize, 5.0f64)],
1711 vec![],
1712 ];
1713 let delta_dist = gpu_sssp_delta_stepping(&adj, 0, 2.0, &GpuBfsConfig::default())
1714 .expect("delta stepping failed");
1715 let ref_dist = dijkstra_ref(&adj, 0);
1716 for i in 0..5 {
1717 if ref_dist[i] == f64::INFINITY {
1718 assert_eq!(delta_dist[i], f64::INFINITY);
1719 } else {
1720 assert!(
1721 (ref_dist[i] - delta_dist[i]).abs() < 1e-9,
1722 "node {}: ref={}, delta={}",
1723 i,
1724 ref_dist[i],
1725 delta_dist[i]
1726 );
1727 }
1728 }
1729 }
1730
1731 #[test]
1732 fn test_delta_stepping_negative_weight_error() {
1733 let adj = vec![vec![(1usize, -1.0f64)], vec![]];
1734 assert!(gpu_sssp_delta_stepping(&adj, 0, 1.0, &GpuBfsConfig::default()).is_err());
1735 }
1736
1737 #[test]
1738 fn test_delta_stepping_invalid_source() {
1739 let adj = vec![vec![(1usize, 1.0f64)], vec![]];
1740 assert!(gpu_sssp_delta_stepping(&adj, 5, 1.0, &GpuBfsConfig::default()).is_err());
1741 }
1742
1743 #[test]
1744 fn test_delta_stepping_invalid_delta() {
1745 let adj = vec![vec![(1usize, 1.0f64)], vec![]];
1746 assert!(gpu_sssp_delta_stepping(&adj, 0, -1.0, &GpuBfsConfig::default()).is_err());
1747 assert!(gpu_sssp_delta_stepping(&adj, 0, 0.0, &GpuBfsConfig::default()).is_err());
1748 }
1749
1750 #[test]
1751 fn test_delta_stepping_disconnected() {
1752 let adj = vec![
1753 vec![(1usize, 1.0f64)],
1754 vec![],
1755 vec![(3usize, 2.0f64)],
1756 vec![],
1757 ];
1758 let dist = gpu_sssp_delta_stepping(&adj, 0, 1.0, &GpuBfsConfig::default()).expect("failed");
1759 assert!((dist[0] - 0.0).abs() < 1e-10);
1760 assert!((dist[1] - 1.0).abs() < 1e-10);
1761 assert_eq!(dist[2], f64::INFINITY);
1762 assert_eq!(dist[3], f64::INFINITY);
1763 }
1764
1765 #[test]
1766 fn test_parallel_bfs_atomic_matches_bfs() {
1767 let (rp, ci) = build_csr(5, &[(0, 1), (1, 2), (2, 3), (3, 4)]);
1768 let bfs_dist = gpu_bfs(&rp, &ci, 0, &GpuBfsConfig::default()).expect("bfs failed");
1769 let atomic_dist = parallel_bfs_atomic(&rp, &ci, 0);
1770 assert_eq!(bfs_dist, atomic_dist);
1771 }
1772}