1use crate::kernels::Kernel;
9use memmap2::{Mmap, MmapMut, MmapOptions};
10use scirs2_core::ndarray::Array2;
11use sklears_core::{
12 error::{Result, SklearsError},
13 types::Float,
14};
15use std::collections::HashMap;
16use std::fs::{File, OpenOptions};
17use std::io::{Seek, SeekFrom, Write};
18use std::path::PathBuf;
19use std::sync::{Arc, Mutex};
20
21#[derive(Debug, Clone)]
23pub struct MemoryMappedKernelConfig {
24 pub file_path: PathBuf,
26 pub cache_size: usize,
28 pub block_size: usize,
30 pub precompute_all: bool,
32 pub compression_level: u8,
34 pub read_only: bool,
36 pub num_threads: usize,
38}
39
40impl Default for MemoryMappedKernelConfig {
41 fn default() -> Self {
42 #[cfg(feature = "parallel")]
43 let default_threads = rayon::current_num_threads();
44 #[cfg(not(feature = "parallel"))]
45 let default_threads = num_cpus::get();
46
47 Self {
48 file_path: std::env::temp_dir().join("sklears_kernel_matrix.dat"),
49 cache_size: 10000,
50 block_size: 1000,
51 precompute_all: false,
52 compression_level: 0,
53 read_only: false,
54 num_threads: default_threads,
55 }
56 }
57}
58
59pub struct MemoryMappedKernelMatrix {
61 config: MemoryMappedKernelConfig,
62 file: File,
63 mmap: Option<Mmap>,
64 mmap_mut: Option<MmapMut>,
65 dimensions: (usize, usize),
66 kernel: Box<dyn Kernel>,
67 cache: Arc<Mutex<LRUCache<(usize, usize), Float>>>,
68 x_data: Option<Array2<Float>>, header_size: usize,
70}
71
72impl MemoryMappedKernelMatrix {
73 pub fn new(
75 kernel: Box<dyn Kernel>,
76 dimensions: (usize, usize),
77 config: MemoryMappedKernelConfig,
78 ) -> Result<Self> {
79 let header_size = std::mem::size_of::<KernelMatrixHeader>();
80
81 let file = if config.read_only {
82 File::open(&config.file_path)?
83 } else {
84 OpenOptions::new()
85 .create(true)
86 .truncate(true)
87 .read(true)
88 .write(true)
89 .open(&config.file_path)?
90 };
91
92 let cache = Arc::new(Mutex::new(LRUCache::new(config.cache_size)));
93
94 let mut matrix = Self {
95 config,
96 file,
97 mmap: None,
98 mmap_mut: None,
99 dimensions,
100 kernel,
101 cache,
102 x_data: None,
103 header_size,
104 };
105
106 matrix.initialize()?;
107 Ok(matrix)
108 }
109
110 fn initialize(&mut self) -> Result<()> {
112 let total_elements = self.dimensions.0 * self.dimensions.1;
113 let data_size = total_elements * std::mem::size_of::<Float>();
114 let total_size = self.header_size + data_size;
115
116 self.file.set_len(total_size as u64)?;
118
119 let header = KernelMatrixHeader {
121 rows: self.dimensions.0 as u64,
122 cols: self.dimensions.1 as u64,
123 element_size: std::mem::size_of::<Float>() as u32,
124 compression_level: self.config.compression_level,
125 version: 1,
126 };
127
128 self.write_header(&header)?;
129
130 if self.config.read_only {
132 let mmap = unsafe {
133 MmapOptions::new()
134 .offset(self.header_size as u64)
135 .len(data_size)
136 .map(&self.file)?
137 };
138 self.mmap = Some(mmap);
139 } else {
140 let mmap_mut = unsafe {
141 MmapOptions::new()
142 .offset(self.header_size as u64)
143 .len(data_size)
144 .map_mut(&self.file)?
145 };
146 self.mmap_mut = Some(mmap_mut);
147 }
148
149 Ok(())
150 }
151
152 pub fn set_data(&mut self, x: Array2<Float>) {
154 self.x_data = Some(x);
155 }
156
157 pub fn precompute(&mut self) -> Result<()> {
159 if self.x_data.is_none() {
160 return Err(SklearsError::InvalidInput(
161 "No data provided for precomputation".to_string(),
162 ));
163 }
164
165 let x = self
166 .x_data
167 .as_ref()
168 .expect("x_data not available - model not fitted")
169 .clone();
170 let (rows, cols) = self.dimensions;
171
172 for i in 0..rows {
174 for j in 0..cols {
175 let k_val = if i <= j {
176 self.kernel.compute(x.row(i), x.row(j))
178 } else {
179 self.get_raw(j, i)?
181 };
182
183 self.set_raw(i, j, k_val)?;
184 if i != j {
185 self.set_raw(j, i, k_val)?; }
187 }
188 }
189
190 self.flush()?;
191 Ok(())
192 }
193
194 pub fn get(&self, i: usize, j: usize) -> Result<Float> {
196 if i >= self.dimensions.0 || j >= self.dimensions.1 {
197 return Err(SklearsError::InvalidInput(format!(
198 "Index ({}, {}) out of bounds for matrix of size ({}, {})",
199 i, j, self.dimensions.0, self.dimensions.1
200 )));
201 }
202
203 if let Ok(mut cache) = self.cache.lock() {
205 if let Some(&value) = cache.get(&(i, j)) {
206 return Ok(value);
207 }
208 }
209
210 let value = if let Ok(precomputed) = self.get_raw(i, j) {
212 precomputed
213 } else if let Some(ref x) = self.x_data {
214 let val = self.kernel.compute(x.row(i), x.row(j));
216
217 if let Ok(mut cache) = self.cache.lock() {
219 cache.put((i, j), val);
220 if i != j {
221 cache.put((j, i), val); }
223 }
224
225 val
226 } else {
227 return Err(SklearsError::InvalidInput(
228 "No precomputed data or original data available".to_string(),
229 ));
230 };
231
232 Ok(value)
233 }
234
235 pub fn set(&mut self, i: usize, j: usize, value: Float) -> Result<()> {
237 if i >= self.dimensions.0 || j >= self.dimensions.1 {
238 return Err(SklearsError::InvalidInput(format!(
239 "Index ({}, {}) out of bounds",
240 i, j
241 )));
242 }
243
244 self.set_raw(i, j, value)?;
245
246 if let Ok(mut cache) = self.cache.lock() {
248 cache.put((i, j), value);
249 }
250
251 Ok(())
252 }
253
254 pub fn get_block(
256 &self,
257 row_start: usize,
258 row_end: usize,
259 col_start: usize,
260 col_end: usize,
261 ) -> Result<Array2<Float>> {
262 if row_end > self.dimensions.0 || col_end > self.dimensions.1 {
263 return Err(SklearsError::InvalidInput(
264 "Block indices out of bounds".to_string(),
265 ));
266 }
267
268 let block_rows = row_end - row_start;
269 let block_cols = col_end - col_start;
270 let mut block = Array2::zeros((block_rows, block_cols));
271
272 for i in 0..block_rows {
273 for j in 0..block_cols {
274 block[[i, j]] = self.get(row_start + i, col_start + j)?;
275 }
276 }
277
278 Ok(block)
279 }
280
281 pub fn set_block(
283 &mut self,
284 row_start: usize,
285 col_start: usize,
286 block: &Array2<Float>,
287 ) -> Result<()> {
288 let (block_rows, block_cols) = block.dim();
289
290 if row_start + block_rows > self.dimensions.0 || col_start + block_cols > self.dimensions.1
291 {
292 return Err(SklearsError::InvalidInput(
293 "Block would exceed matrix bounds".to_string(),
294 ));
295 }
296
297 for i in 0..block_rows {
298 for j in 0..block_cols {
299 self.set(row_start + i, col_start + j, block[[i, j]])?;
300 }
301 }
302
303 Ok(())
304 }
305
306 fn get_raw(&self, i: usize, j: usize) -> Result<Float> {
308 let offset = (i * self.dimensions.1 + j) * std::mem::size_of::<Float>();
309
310 if let Some(ref mmap) = self.mmap {
311 let bytes = &mmap[offset..offset + std::mem::size_of::<Float>()];
312 let value =
313 Float::from_le_bytes(bytes.try_into().map_err(|_| {
314 SklearsError::InvalidInput("Failed to read raw value".to_string())
315 })?);
316 Ok(value)
317 } else if let Some(ref mmap_mut) = self.mmap_mut {
318 let bytes = &mmap_mut[offset..offset + std::mem::size_of::<Float>()];
319 let value =
320 Float::from_le_bytes(bytes.try_into().map_err(|_| {
321 SklearsError::InvalidInput("Failed to read raw value".to_string())
322 })?);
323 Ok(value)
324 } else {
325 Err(SklearsError::InvalidInput(
326 "No memory mapping available".to_string(),
327 ))
328 }
329 }
330
331 fn set_raw(&mut self, i: usize, j: usize, value: Float) -> Result<()> {
333 let offset = (i * self.dimensions.1 + j) * std::mem::size_of::<Float>();
334
335 if let Some(ref mut mmap_mut) = self.mmap_mut {
336 let bytes = value.to_le_bytes();
337 mmap_mut[offset..offset + std::mem::size_of::<Float>()].copy_from_slice(&bytes);
338 Ok(())
339 } else {
340 Err(SklearsError::InvalidInput(
341 "Memory mapping not available for writing".to_string(),
342 ))
343 }
344 }
345
346 fn write_header(&mut self, header: &KernelMatrixHeader) -> Result<()> {
348 self.file.seek(SeekFrom::Start(0))?;
349
350 let header_bytes = unsafe {
351 std::slice::from_raw_parts(
352 header as *const KernelMatrixHeader as *const u8,
353 std::mem::size_of::<KernelMatrixHeader>(),
354 )
355 };
356
357 self.file.write_all(header_bytes)?;
358
359 Ok(())
360 }
361
362 pub fn flush(&mut self) -> Result<()> {
364 if let Some(ref mmap_mut) = self.mmap_mut {
365 mmap_mut.flush()?;
366 }
367 Ok(())
368 }
369
370 pub fn dimensions(&self) -> (usize, usize) {
372 self.dimensions
373 }
374
375 pub fn cache_stats(&self) -> Result<CacheStats> {
377 if let Ok(cache) = self.cache.lock() {
378 Ok(CacheStats {
379 size: cache.len(),
380 capacity: cache.cap(),
381 hit_rate: 0.0, })
383 } else {
384 Err(SklearsError::InvalidInput(
385 "Failed to access cache".to_string(),
386 ))
387 }
388 }
389}
390
391#[repr(C)]
393#[derive(Debug, Clone, Copy)]
394struct KernelMatrixHeader {
395 rows: u64,
396 cols: u64,
397 element_size: u32,
398 compression_level: u8,
399 version: u8,
400}
401
402pub struct CacheStats {
404 pub size: usize,
405 pub capacity: usize,
406 pub hit_rate: f64,
407}
408
409pub struct SparseMemoryMappedKernelMatrix {
411 dense_matrix: MemoryMappedKernelMatrix,
412 sparse_indices: HashMap<(usize, usize), usize>,
413 sparsity_threshold: Float,
414}
415
416impl SparseMemoryMappedKernelMatrix {
417 pub fn new(
419 kernel: Box<dyn Kernel>,
420 dimensions: (usize, usize),
421 config: MemoryMappedKernelConfig,
422 sparsity_threshold: Float,
423 ) -> Result<Self> {
424 let dense_matrix = MemoryMappedKernelMatrix::new(kernel, dimensions, config)?;
425
426 Ok(Self {
427 dense_matrix,
428 sparse_indices: HashMap::new(),
429 sparsity_threshold,
430 })
431 }
432
433 pub fn get(&self, i: usize, j: usize) -> Result<Float> {
435 if let Some(&sparse_idx) = self.sparse_indices.get(&(i, j)) {
436 self.dense_matrix.get_raw(
438 sparse_idx / self.dense_matrix.dimensions.1,
439 sparse_idx % self.dense_matrix.dimensions.1,
440 )
441 } else {
442 let value = self.dense_matrix.get(i, j)?;
444 if value.abs() < self.sparsity_threshold {
445 Ok(0.0)
446 } else {
447 Ok(value)
448 }
449 }
450 }
451
452 pub fn set(&mut self, i: usize, j: usize, value: Float) -> Result<()> {
454 if value.abs() < self.sparsity_threshold {
455 self.sparse_indices.remove(&(i, j));
457 Ok(())
458 } else {
459 self.dense_matrix.set(i, j, value)
460 }
461 }
462
463 pub fn sparsity_ratio(&self) -> f64 {
465 let total_elements = self.dense_matrix.dimensions.0 * self.dense_matrix.dimensions.1;
466 let sparse_elements = self.sparse_indices.len();
467 1.0 - (sparse_elements as f64 / total_elements as f64)
468 }
469}
470
471struct LRUCache<K, V> {
473 map: HashMap<K, V>,
474 capacity: usize,
475}
476
477impl<K: std::hash::Hash + Eq + Clone, V: Clone> LRUCache<K, V> {
478 fn new(capacity: usize) -> Self {
479 Self {
480 map: HashMap::with_capacity(capacity),
481 capacity,
482 }
483 }
484
485 fn get(&mut self, key: &K) -> Option<&V> {
486 self.map.get(key)
487 }
488
489 fn put(&mut self, key: K, value: V) {
490 if self.map.len() >= self.capacity {
491 if let Some(key_to_remove) = self.map.keys().next().cloned() {
493 self.map.remove(&key_to_remove);
494 }
495 }
496 self.map.insert(key, value);
497 }
498
499 fn len(&self) -> usize {
500 self.map.len()
501 }
502
503 fn cap(&self) -> usize {
504 self.capacity
505 }
506}
507
508#[allow(non_snake_case)]
509#[cfg(test)]
510mod tests {
511 use super::*;
512 use crate::kernels::LinearKernel;
513
514 use tempfile::NamedTempFile;
515
516 #[test]
517 fn test_memory_mapped_kernel_creation() {
518 let temp_file = NamedTempFile::new().expect("construction should succeed");
519 let config = MemoryMappedKernelConfig {
520 file_path: temp_file.path().to_path_buf(),
521 ..MemoryMappedKernelConfig::default()
522 };
523
524 let kernel = Box::new(LinearKernel);
525 let matrix = MemoryMappedKernelMatrix::new(kernel, (100, 100), config);
526 assert!(matrix.is_ok());
527 }
528
529 #[test]
530 fn test_kernel_value_set_get() {
531 let temp_file = NamedTempFile::new().expect("construction should succeed");
532 let config = MemoryMappedKernelConfig {
533 file_path: temp_file.path().to_path_buf(),
534 ..MemoryMappedKernelConfig::default()
535 };
536
537 let kernel = Box::new(LinearKernel);
538 let mut matrix = MemoryMappedKernelMatrix::new(kernel, (10, 10), config)
539 .expect("construction should succeed");
540
541 matrix.set(0, 0, 1.5).expect("operation should succeed");
542 assert_eq!(matrix.get(0, 0).expect("operation should succeed"), 1.5);
543 }
544
545 #[test]
546 fn test_sparse_memory_mapped_kernel() {
547 let temp_file = NamedTempFile::new().expect("construction should succeed");
548 let config = MemoryMappedKernelConfig {
549 file_path: temp_file.path().to_path_buf(),
550 ..MemoryMappedKernelConfig::default()
551 };
552
553 let kernel = Box::new(LinearKernel);
554 let sparse_matrix = SparseMemoryMappedKernelMatrix::new(kernel, (10, 10), config, 0.1);
555 assert!(sparse_matrix.is_ok());
556 }
557}