Skip to main content

oxigdal_algorithms/parallel/
mod.rs

1//! Parallel processing support for multi-core performance
2//!
3//! This module provides parallel implementations of common geospatial operations
4//! using Rayon for work-stealing and efficient multi-threaded execution.
5//!
6//! # Features
7//!
8//! This module is only available when the `parallel` feature is enabled.
9//!
10//! ## Parallel Raster Operations
11//!
12//! - `parallel_map_raster`: Apply a function to each pixel in parallel
13//! - `parallel_reduce_raster`: Aggregate statistics from chunks
14//! - `parallel_transform_raster`: Apply transformations using thread pool
15//!
16//! ## Parallel Tile Processing
17//!
18//! - Process multiple tiles concurrently
19//! - COG pyramid generation in parallel
20//! - Overview computation (multiple levels simultaneously)
21//! - Tile compression in parallel
22//!
23//! ## Batch Processing
24//!
25//! - Process multiple files in parallel
26//! - Configurable thread pool size
27//! - Thread-safe result collection
28//!
29//! # Example
30//!
31//! ```no_run
32//! # #[cfg(feature = "parallel")]
33//! # {
34//! use oxigdal_algorithms::parallel::*;
35//! use oxigdal_core::buffer::RasterBuffer;
36//! use oxigdal_core::types::RasterDataType;
37//! # use oxigdal_algorithms::error::Result;
38//!
39//! # fn main() -> Result<()> {
40//! // Create a raster buffer
41//! let buffer = RasterBuffer::zeros(1000, 1000, RasterDataType::Float32);
42//!
43//! // Apply a parallel operation
44//! let result = parallel_map_raster(&buffer, |pixel| pixel * 2.0)?;
45//! # Ok(())
46//! # }
47//! # }
48//! ```
49//!
50//! # Performance
51//!
52//! Parallel operations provide 2-16x speedup on multi-core systems, depending on:
53//!
54//! - Number of CPU cores
55//! - Data size (larger datasets benefit more)
56//! - Operation complexity (heavier operations benefit more)
57//! - Memory bandwidth
58//!
59//! # Thread Safety
60//!
61//! All parallel operations are thread-safe and use proper synchronization.
62//! Errors are collected and returned in a thread-safe manner.
63//!
64//! # COOLJAPAN Policy Compliance
65//!
66//! - Pure Rust (no C/Fortran dependencies)
67//! - No `unwrap()` or `expect()` in production code
68//! - Feature-gated (default OFF to keep dependencies minimal)
69//! - Comprehensive error handling
70
71pub mod batch;
72pub mod raster;
73pub mod tiles;
74
75// Re-export commonly used items
76pub use batch::{BatchConfig, BatchResult, parallel_batch_process, parallel_map};
77pub use raster::{
78    ChunkConfig, ReduceOp, parallel_focal_mean, parallel_focal_median, parallel_map_raster,
79    parallel_map_raster_with_config, parallel_reduce_raster, parallel_transform_raster,
80};
81
82#[cfg(feature = "parallel")]
83pub use raster::{FocalOp, focal_parallel, hillshade_parallel, slope_parallel};
84pub use tiles::{TileConfig, TileProcessor, parallel_generate_overviews, parallel_process_tiles};
85
86/// Configuration for parallel processing
87#[derive(Debug, Clone)]
88pub struct ParallelConfig {
89    /// Number of threads to use (None = default from Rayon)
90    pub num_threads: Option<usize>,
91    /// Chunk size for raster operations (None = automatic)
92    pub chunk_size: Option<usize>,
93    /// Enable progress reporting
94    pub progress: bool,
95}
96
97impl Default for ParallelConfig {
98    fn default() -> Self {
99        Self {
100            num_threads: None,
101            chunk_size: None,
102            progress: false,
103        }
104    }
105}
106
107impl ParallelConfig {
108    /// Creates a new parallel configuration
109    #[must_use]
110    pub const fn new() -> Self {
111        Self {
112            num_threads: None,
113            chunk_size: None,
114            progress: false,
115        }
116    }
117
118    /// Sets the number of threads
119    #[must_use]
120    pub const fn with_threads(mut self, num_threads: usize) -> Self {
121        self.num_threads = Some(num_threads);
122        self
123    }
124
125    /// Sets the chunk size
126    #[must_use]
127    pub const fn with_chunk_size(mut self, chunk_size: usize) -> Self {
128        self.chunk_size = Some(chunk_size);
129        self
130    }
131
132    /// Enables progress reporting
133    #[must_use]
134    pub const fn with_progress(mut self, progress: bool) -> Self {
135        self.progress = progress;
136        self
137    }
138}
139
140/// Calculates optimal chunk size based on data size and CPU count
141///
142/// This function determines an appropriate chunk size for parallel operations
143/// to maximize cache locality and minimize overhead.
144///
145/// # Arguments
146///
147/// * `total_elements` - Total number of elements to process
148/// * `num_threads` - Number of threads (None = use CPU count)
149///
150/// # Returns
151///
152/// Optimal chunk size in elements
153#[must_use]
154pub fn calculate_chunk_size(total_elements: usize, num_threads: Option<usize>) -> usize {
155    let threads = num_threads.unwrap_or_else(num_cpus);
156
157    // Aim for 4-8 chunks per thread for good load balancing
158    let target_chunks = threads * 6;
159
160    // Minimum chunk size for cache efficiency (64 KB)
161    const MIN_CHUNK_SIZE: usize = 64 * 1024 / core::mem::size_of::<f64>();
162
163    // Maximum chunk size (4 MB)
164    const MAX_CHUNK_SIZE: usize = 4 * 1024 * 1024 / core::mem::size_of::<f64>();
165
166    let chunk_size = total_elements / target_chunks;
167
168    // Clamp to reasonable bounds
169    chunk_size.clamp(MIN_CHUNK_SIZE, MAX_CHUNK_SIZE)
170}
171
172/// Returns the number of available CPUs
173#[must_use]
174fn num_cpus() -> usize {
175    rayon::current_num_threads()
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181
182    #[test]
183    fn test_parallel_config_default() {
184        let config = ParallelConfig::default();
185        assert!(config.num_threads.is_none());
186        assert!(config.chunk_size.is_none());
187        assert!(!config.progress);
188    }
189
190    #[test]
191    fn test_parallel_config_builder() {
192        let config = ParallelConfig::new()
193            .with_threads(4)
194            .with_chunk_size(1024)
195            .with_progress(true);
196
197        assert_eq!(config.num_threads, Some(4));
198        assert_eq!(config.chunk_size, Some(1024));
199        assert!(config.progress);
200    }
201
202    #[test]
203    fn test_calculate_chunk_size() {
204        let chunk_size = calculate_chunk_size(1_000_000, Some(4));
205        assert!(chunk_size > 0);
206        assert!(chunk_size < 1_000_000);
207    }
208
209    #[test]
210    fn test_num_cpus() {
211        let cpus = num_cpus();
212        assert!(cpus > 0);
213    }
214}