Skip to main content

oxiphysics_gpu/parallel/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5/// Result of load balancing: a set of ranges assigned to workers.
6pub struct LoadBalancePlan {
7    /// Ranges for each worker.
8    pub ranges: Vec<std::ops::Range<usize>>,
9    /// Total weight assigned to each worker (for Weighted strategy).
10    pub weights: Vec<f64>,
11}
12impl LoadBalancePlan {
13    /// Number of workers in this plan.
14    pub fn num_workers(&self) -> usize {
15        self.ranges.len()
16    }
17    /// Maximum weight across workers (a measure of imbalance).
18    pub fn max_weight(&self) -> f64 {
19        self.weights
20            .iter()
21            .copied()
22            .fold(f64::NEG_INFINITY, f64::max)
23    }
24    /// Imbalance ratio: max_weight / avg_weight. 1.0 is perfect balance.
25    pub fn imbalance_ratio(&self) -> f64 {
26        if self.weights.is_empty() {
27            return 1.0;
28        }
29        let total: f64 = self.weights.iter().sum();
30        let avg = total / self.weights.len() as f64;
31        if avg < 1e-15 {
32            return 1.0;
33        }
34        self.max_weight() / avg
35    }
36}
37/// A simple work-stealing queue for task-parallel scheduling.
38///
39/// Internally backed by a `Vec`T` with a front/back cursor pair.  "Stealing"
40/// takes from the front (like a deque), while the owner pushes/pops from the
41/// back.
42pub struct WorkStealQueue<T> {
43    pub(super) items: std::collections::VecDeque<T>,
44}
45impl<T: Send> WorkStealQueue<T> {
46    /// Create an empty work-steal queue.
47    pub fn new() -> Self {
48        Self {
49            items: std::collections::VecDeque::new(),
50        }
51    }
52    /// Push a task onto the owner end (back).
53    pub fn push(&mut self, task: T) {
54        self.items.push_back(task);
55    }
56    /// Pop a task from the owner end (back).  Returns `None` if empty.
57    pub fn pop(&mut self) -> Option<T> {
58        self.items.pop_back()
59    }
60    /// Steal a task from the thief end (front).  Returns `None` if empty.
61    pub fn steal(&mut self) -> Option<T> {
62        self.items.pop_front()
63    }
64    /// Number of pending tasks.
65    pub fn len(&self) -> usize {
66        self.items.len()
67    }
68    /// Whether the queue is empty.
69    pub fn is_empty(&self) -> bool {
70        self.items.is_empty()
71    }
72}
73/// Configuration for choosing optimal work group sizes.
74///
75/// Models GPU-like work group sizing where the total work is divided into
76/// groups of a fixed size, potentially with padding in the last group.
77pub struct WorkGroupConfig {
78    /// Preferred work group size (e.g. 64, 128, 256).
79    pub preferred_size: usize,
80    /// Maximum work group size supported.
81    pub max_size: usize,
82    /// Minimum work group size (avoid groups too small for efficiency).
83    pub min_size: usize,
84}
85impl WorkGroupConfig {
86    /// Create a new config with preferred group size.
87    pub fn new(preferred_size: usize) -> Self {
88        Self {
89            preferred_size: preferred_size.max(1),
90            max_size: 1024,
91            min_size: 32,
92        }
93    }
94    /// Create a default config suitable for CPU-side Rayon parallelism.
95    pub fn cpu_default() -> Self {
96        let threads = rayon::current_num_threads().max(1);
97        Self {
98            preferred_size: 64,
99            max_size: 1024,
100            min_size: threads,
101        }
102    }
103    /// Compute the optimal work group size for `total` items.
104    ///
105    /// Returns a size in `\[min_size, max_size\]` that balances occupancy.
106    /// Prefers `preferred_size` but adjusts if `total` is small.
107    pub fn optimal_size(&self, total: usize) -> usize {
108        if total == 0 {
109            return self.min_size;
110        }
111        if total <= self.preferred_size {
112            return total.max(self.min_size).min(self.max_size);
113        }
114        let preferred_groups = total.div_ceil(self.preferred_size);
115        let preferred_waste = preferred_groups * self.preferred_size - total;
116        let preferred_waste_ratio = preferred_waste as f64 / total as f64;
117        if preferred_waste_ratio < 0.25 {
118            return self.preferred_size;
119        }
120        let mut best_size = self.preferred_size;
121        let mut best_waste = preferred_waste;
122        for candidate in (self.min_size..=self.max_size).step_by(self.min_size) {
123            let groups = total.div_ceil(candidate);
124            let waste = groups * candidate - total;
125            if waste < best_waste {
126                best_waste = waste;
127                best_size = candidate;
128            }
129        }
130        best_size
131    }
132    /// Compute the number of work groups needed for `total` items.
133    pub fn num_groups(&self, total: usize) -> usize {
134        let size = self.optimal_size(total);
135        total.div_ceil(size)
136    }
137    /// Return ranges for each work group covering `0..total`.
138    pub fn group_ranges(&self, total: usize) -> Vec<std::ops::Range<usize>> {
139        let size = self.optimal_size(total);
140        (0..total)
141            .step_by(size.max(1))
142            .map(|start| start..(start + size).min(total))
143            .collect()
144    }
145}
146/// Load balancing strategy for distributing work across threads.
147#[derive(Debug, Clone, Copy, PartialEq)]
148pub enum LoadBalanceStrategy {
149    /// Static: divide work evenly by index count.
150    Static,
151    /// Weighted: divide work so each thread gets roughly equal weight.
152    Weighted,
153    /// Guided: start with large chunks, decrease chunk size as work progresses.
154    Guided,
155}
156/// Splits `n` particle indices into chunks suitable for parallel work.
157///
158/// `chunk_size` is chosen so that each Rayon worker thread gets at least one
159/// chunk.
160pub struct WorkChunker {
161    /// Total number of items.
162    pub n: usize,
163    /// Size of each chunk.
164    pub chunk_size: usize,
165}
166impl WorkChunker {
167    /// Create a new `WorkChunker` for `n` items.
168    ///
169    /// `chunk_size` is set to `n / rayon::current_num_threads() + 1`.
170    pub fn new(n: usize) -> Self {
171        let threads = rayon::current_num_threads().max(1);
172        let chunk_size = n / threads + 1;
173        Self { n, chunk_size }
174    }
175    /// Return contiguous index ranges covering `0..n` without gaps or overlaps.
176    pub fn chunks(&self) -> Vec<std::ops::Range<usize>> {
177        let cs = self.chunk_size.max(1);
178        (0..self.n)
179            .step_by(cs)
180            .map(|start| start..(start + cs).min(self.n))
181            .collect()
182    }
183}