1use crate::common::IntegrateFloat;
7use crate::error::{IntegrateError, IntegrateResult};
8use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ArrayView2};
9use scirs2_core::parallel_ops::*;
10use std::collections::{HashMap, HashSet};
11
12#[derive(Debug, Clone)]
14pub struct SparsePattern {
15 pub entries: Vec<(usize, usize)>,
17 pub nrows: usize,
19 pub n_cols: usize,
21 pub row_indices: Vec<Vec<usize>>,
23 pub col_indices: Vec<Vec<usize>>,
25}
26
27impl SparsePattern {
28 pub fn new(_nrows: usize, ncols: usize) -> Self {
30 SparsePattern {
31 entries: Vec::new(),
32 nrows: _nrows,
33 n_cols: ncols,
34 row_indices: vec![Vec::new(); _nrows],
35 col_indices: vec![Vec::new(); ncols],
36 }
37 }
38
39 pub fn add_entry(&mut self, row: usize, col: usize) {
41 if row < self.nrows && col < self.n_cols {
42 self.entries.push((row, col));
43 self.row_indices[row].push(col);
44 self.col_indices[col].push(row);
45 }
46 }
47
48 pub fn nnz(&self) -> usize {
50 self.entries.len()
51 }
52
53 pub fn sparsity(&self) -> f64 {
55 let total = (self.nrows * self.n_cols) as f64;
56 if total > 0.0 {
57 1.0 - (self.nnz() as f64 / total)
58 } else {
59 0.0
60 }
61 }
62
63 pub fn is_sparse(&self, threshold: f64) -> bool {
65 self.sparsity() > threshold
66 }
67
68 pub fn compute_coloring(&self) -> ColGrouping {
70 let mut degrees: Vec<(usize, usize)> = Vec::new();
72
73 for col in 0..self.n_cols {
75 let mut neighbors = HashSet::new();
76 for &row in &self.col_indices[col] {
77 for &other_col in &self.row_indices[row] {
78 if other_col != col {
79 neighbors.insert(other_col);
80 }
81 }
82 }
83 degrees.push((col, neighbors.len()));
84 }
85
86 degrees.sort_by_key(|&(_, deg)| std::cmp::Reverse(deg));
88
89 let mut colors: HashMap<usize, usize> = HashMap::new();
90 let mut max_color = 0;
91
92 for (col_, _) in degrees {
94 let mut used_colors = HashSet::new();
95
96 for &row in &self.col_indices[col_] {
98 for &other_col in &self.row_indices[row] {
99 if other_col != col_ {
100 if let Some(&color) = colors.get(&other_col) {
101 used_colors.insert(color);
102 }
103 }
104 }
105 }
106
107 let mut color = 0;
109 while used_colors.contains(&color) {
110 color += 1;
111 }
112
113 colors.insert(col_, color);
114 max_color = max_color.max(color);
115 }
116
117 let mut groups = vec![Vec::new(); max_color + 1];
119 for (&col, &color) in &colors {
120 groups[color].push(col);
121 }
122
123 ColGrouping { groups }
124 }
125}
126
127pub struct ColGrouping {
129 pub groups: Vec<Vec<usize>>,
131}
132
133impl ColGrouping {
134 pub fn n_groups(&self) -> usize {
136 self.groups.len()
137 }
138}
139
140pub struct SparseJacobian<F: IntegrateFloat> {
142 pub pattern: SparsePattern,
144 pub values: Vec<F>,
146 pub index_map: HashMap<(usize, usize), usize>,
148}
149
150impl<F: IntegrateFloat> SparseJacobian<F> {
151 pub fn new(pattern: SparsePattern) -> Self {
153 let nnz = pattern.nnz();
154 let mut index_map = HashMap::new();
155
156 for (i, &(row, col)) in pattern.entries.iter().enumerate() {
157 index_map.insert((row, col), i);
158 }
159
160 SparseJacobian {
161 pattern,
162 values: vec![F::zero(); nnz],
163 index_map,
164 }
165 }
166
167 pub fn set(&mut self, row: usize, col: usize, value: F) -> IntegrateResult<()> {
169 if let Some(&idx) = self.index_map.get(&(row, col)) {
170 self.values[idx] = value;
171 Ok(())
172 } else {
173 Err(IntegrateError::IndexError(format!(
174 "Entry ({row}, {col}) not in sparsity pattern"
175 )))
176 }
177 }
178
179 pub fn get(&self, row: usize, col: usize) -> Option<F> {
181 self.index_map.get(&(row, col)).map(|&idx| self.values[idx])
182 }
183
184 pub fn to_dense(&self) -> Array2<F> {
186 let mut dense = Array2::zeros((self.pattern.nrows, self.pattern.n_cols));
187 for (&(row, col), &idx) in &self.index_map {
188 dense[[row, col]] = self.values[idx];
189 }
190 dense
191 }
192
193 pub fn matvec(&self, x: ArrayView1<F>) -> IntegrateResult<Array1<F>> {
195 if x.len() != self.pattern.n_cols {
196 return Err(IntegrateError::DimensionMismatch(format!(
197 "Expected {} columns, got {}",
198 self.pattern.n_cols,
199 x.len()
200 )));
201 }
202
203 let mut y = Array1::zeros(self.pattern.nrows);
204
205 for (&(row, col), &idx) in &self.index_map {
206 y[row] += self.values[idx] * x[col];
207 }
208
209 Ok(y)
210 }
211
212 pub fn matvec_transpose(&self, x: ArrayView1<F>) -> IntegrateResult<Array1<F>> {
214 if x.len() != self.pattern.nrows {
215 return Err(IntegrateError::DimensionMismatch(format!(
216 "Expected {} rows, got {}",
217 self.pattern.nrows,
218 x.len()
219 )));
220 }
221
222 let mut y = Array1::zeros(self.pattern.n_cols);
223
224 for (&(row, col), &idx) in &self.index_map {
225 y[col] += self.values[idx] * x[row];
226 }
227
228 Ok(y)
229 }
230}
231
232pub struct CSRJacobian<F: IntegrateFloat> {
234 pub nrows: usize,
236 pub n_cols: usize,
238 pub row_ptr: Vec<usize>,
240 pub col_idx: Vec<usize>,
242 pub values: Vec<F>,
244}
245
246pub struct CSCJacobian<F: IntegrateFloat> {
248 pub nrows: usize,
250 pub n_cols: usize,
252 pub col_ptr: Vec<usize>,
254 pub row_idx: Vec<usize>,
256 pub values: Vec<F>,
258}
259
260impl<F: IntegrateFloat> SparseJacobian<F> {
261 pub fn from_pattern(pattern: SparsePattern) -> Self {
263 let mut index_map = HashMap::new();
264 for (idx, &(row, col)) in pattern.entries.iter().enumerate() {
265 index_map.insert((row, col), idx);
266 }
267
268 SparseJacobian {
269 values: vec![F::zero(); pattern.entries.len()],
270 pattern,
271 index_map,
272 }
273 }
274
275 pub fn set_unchecked(&mut self, row: usize, col: usize, value: F) {
277 if let Some(&idx) = self.index_map.get(&(row, col)) {
278 self.values[idx] = value;
279 }
280 }
281
282 pub fn get_or_zero(&self, row: usize, col: usize) -> F {
284 if let Some(&idx) = self.index_map.get(&(row, col)) {
285 self.values[idx]
286 } else {
287 F::zero()
288 }
289 }
290
291 pub fn to_dense_alt(&self) -> Array2<F> {
293 let mut dense = Array2::zeros((self.pattern.nrows, self.pattern.n_cols));
294 for (idx, &(row, col)) in self.pattern.entries.iter().enumerate() {
295 dense[[row, col]] = self.values[idx];
296 }
297 dense
298 }
299
300 pub fn apply(&self, x: ArrayView1<F>) -> IntegrateResult<Array1<F>> {
302 if x.len() != self.pattern.n_cols {
303 return Err(IntegrateError::DimensionMismatch(format!(
304 "Expected {} columns, got {}",
305 self.pattern.n_cols,
306 x.len()
307 )));
308 }
309
310 let mut result = Array1::zeros(self.pattern.nrows);
311 for (idx, &(row, col)) in self.pattern.entries.iter().enumerate() {
312 result[row] += self.values[idx] * x[col];
313 }
314
315 Ok(result)
316 }
317
318 pub fn to_csr(&self) -> CSRJacobian<F> {
320 let mut entries: Vec<(usize, usize, F)> = Vec::new();
321 for (idx, &(row, col)) in self.pattern.entries.iter().enumerate() {
322 entries.push((row, col, self.values[idx]));
323 }
324
325 entries.sort_by_key(|&(r, c, _)| (r, c));
327
328 let mut row_ptr = vec![0];
329 let mut col_idx = Vec::new();
330 let mut values = Vec::new();
331
332 let mut currentrow = 0;
333 for (row, col, val) in entries {
334 while currentrow < row {
335 row_ptr.push(col_idx.len());
336 currentrow += 1;
337 }
338 col_idx.push(col);
339 values.push(val);
340 }
341
342 while row_ptr.len() <= self.pattern.nrows {
344 row_ptr.push(col_idx.len());
345 }
346
347 CSRJacobian {
348 nrows: self.pattern.nrows,
349 n_cols: self.pattern.n_cols,
350 row_ptr,
351 col_idx,
352 values,
353 }
354 }
355}
356
357#[allow(dead_code)]
359pub fn detect_sparsity<F, Func>(f: Func, x: ArrayView1<F>, eps: F) -> IntegrateResult<SparsePattern>
360where
361 F: IntegrateFloat + Send + Sync,
362 Func: Fn(ArrayView1<F>) -> IntegrateResult<Array1<F>> + Sync,
363{
364 let n = x.len();
365 let f0 = f(x)?;
366 let m = f0.len();
367
368 let mut pattern = SparsePattern::new(m, n);
369
370 let results: Vec<_> = (0..n)
372 .collect::<Vec<_>>()
373 .par_chunks(n / scirs2_core::parallel_ops::num_threads().max(1) + 1)
374 .map(|chunk| {
375 let mut local_entries = Vec::new();
376 for &j in chunk {
377 let mut x_pert = x.to_owned();
378 x_pert[j] += eps;
379 if let Ok(f_pert) = f(x_pert.view()) {
380 for i in 0..m {
381 if (f_pert[i] - f0[i]).abs() > F::epsilon() {
382 local_entries.push((i, j));
383 }
384 }
385 }
386 }
387 local_entries
388 })
389 .collect();
390
391 for entries in results {
393 for (i, j) in entries {
394 pattern.add_entry(i, j);
395 }
396 }
397
398 Ok(pattern)
399}
400
401impl<F: IntegrateFloat + Send + Sync> CSRJacobian<F> {
402 pub fn apply(&self, x: ArrayView1<F>) -> IntegrateResult<Array1<F>> {
404 if x.len() != self.n_cols {
405 return Err(IntegrateError::DimensionMismatch(format!(
406 "Expected {} columns, got {}",
407 self.n_cols,
408 x.len()
409 )));
410 }
411
412 let mut result = Array1::zeros(self.nrows);
413
414 let chunk_size = (self.nrows / scirs2_core::parallel_ops::num_threads()).max(1);
416 let chunks: Vec<_> = (0..self.nrows)
417 .collect::<Vec<_>>()
418 .par_chunks(chunk_size)
419 .map(|rows| {
420 let mut local_result = Array1::zeros(rows.len());
421 for (local_idx, &row) in rows.iter().enumerate() {
422 let start = self.row_ptr[row];
423 let end = self.row_ptr[row + 1];
424 for idx in start..end {
425 local_result[local_idx] += self.values[idx] * x[self.col_idx[idx]];
426 }
427 }
428 (rows[0], local_result)
429 })
430 .collect();
431
432 for (startrow, chunk) in chunks {
434 for (i, val) in chunk.iter().enumerate() {
435 result[startrow + i] = *val;
436 }
437 }
438
439 Ok(result)
440 }
441
442 pub fn transpose(&self) -> CSCJacobian<F> {
444 let mut entries: Vec<(usize, usize, F)> = Vec::new();
445
446 for row in 0..self.nrows {
447 let start = self.row_ptr[row];
448 let end = self.row_ptr[row + 1];
449 for idx in start..end {
450 entries.push((self.col_idx[idx], row, self.values[idx]));
451 }
452 }
453
454 entries.sort_by_key(|&(c, r, _)| (c, r));
456
457 let mut col_ptr = vec![0];
458 let mut row_idx = Vec::new();
459 let mut values = Vec::new();
460
461 let mut current_col = 0;
462 for (col, row, val) in entries {
463 while current_col < col {
464 col_ptr.push(row_idx.len());
465 current_col += 1;
466 }
467 row_idx.push(row);
468 values.push(val);
469 }
470
471 while col_ptr.len() <= self.n_cols {
473 col_ptr.push(row_idx.len());
474 }
475
476 CSCJacobian {
477 nrows: self.n_cols,
478 n_cols: self.nrows,
479 col_ptr,
480 row_idx,
481 values,
482 }
483 }
484}
485
486#[allow(dead_code)]
488pub fn compress_jacobian<F: IntegrateFloat>(
489 dense: ArrayView2<F>,
490 pattern: &SparsePattern,
491) -> SparseJacobian<F> {
492 let mut sparse = SparseJacobian::from_pattern(pattern.clone());
493
494 for (idx, &(row, col)) in pattern.entries.iter().enumerate() {
495 sparse.values[idx] = dense[[row, col]];
496 }
497
498 sparse
499}
500
501#[allow(dead_code)]
503pub fn colored_jacobian<F, Func>(
504 f: Func,
505 x: ArrayView1<F>,
506 pattern: &SparsePattern,
507 eps: F,
508) -> IntegrateResult<SparseJacobian<F>>
509where
510 F: IntegrateFloat,
511 Func: Fn(ArrayView1<F>) -> IntegrateResult<Array1<F>>,
512{
513 let coloring = pattern.compute_coloring();
514 let f0 = f(x)?;
515 let mut jacobian = SparseJacobian::from_pattern(pattern.clone());
516
517 for group in &coloring.groups {
519 let mut x_pert = x.to_owned();
520
521 for &col in group {
523 x_pert[col] += eps;
524 }
525
526 let f_pert = f(x_pert.view())?;
527
528 for &col in group {
530 for &row in &pattern.col_indices[col] {
531 let deriv = (f_pert[row] - f0[row]) / eps;
532 let _ = jacobian.set(row, col, deriv);
533 }
534 }
535 }
536
537 Ok(jacobian)
538}
539
540#[allow(dead_code)]
542pub fn example_tridiagonal_pattern(n: usize) -> SparsePattern {
543 let mut pattern = SparsePattern::new(n, n);
544
545 for i in 0..n {
546 pattern.add_entry(i, i); if i > 0 {
548 pattern.add_entry(i, i - 1); }
550 if i < n - 1 {
551 pattern.add_entry(i, i + 1); }
553 }
554
555 pattern
556}
557
558pub struct SparseJacobianUpdater<F: IntegrateFloat> {
560 pattern: SparsePattern,
561 threshold: F,
562}
563
564impl<F: IntegrateFloat> SparseJacobianUpdater<F> {
565 pub fn new(pattern: SparsePattern, threshold: F) -> Self {
567 SparseJacobianUpdater { pattern, threshold }
568 }
569
570 pub fn broyden_update(
572 &self,
573 jac: &mut SparseJacobian<F>,
574 dx: ArrayView1<F>,
575 df: ArrayView1<F>,
576 ) -> IntegrateResult<()> {
577 let jdx = jac.apply(dx)?;
578 let dy = &df - &jdx;
579
580 let dx_norm_sq = dx.dot(&dx);
581 if dx_norm_sq < self.threshold {
582 return Ok(());
583 }
584
585 for (idx, &(i, j)) in self.pattern.entries.iter().enumerate() {
587 jac.values[idx] += dy[i] * dx[j] / dx_norm_sq;
588 }
589
590 Ok(())
591 }
592}
593
594#[allow(dead_code)]
596pub fn detect_sparsity_adaptive<F, Func>(
597 f: Func,
598 x: ArrayView1<F>,
599 eps_range: (F, F),
600 n_samples: usize,
601) -> IntegrateResult<SparsePattern>
602where
603 F: IntegrateFloat + Send + Sync,
604 Func: Fn(ArrayView1<F>) -> IntegrateResult<Array1<F>> + Sync,
605{
606 let n = x.len();
607 let f0 = f(x)?;
608 let m = f0.len();
609
610 let mut accumulated_pattern = SparsePattern::new(m, n);
611 let eps_min = eps_range.0;
612 let eps_max = eps_range.1;
613
614 for sample in 0..n_samples {
616 let alpha = F::from(sample).unwrap() / F::from(n_samples - 1).unwrap();
617 let eps = eps_min * (F::one() - alpha) + eps_max * alpha;
618
619 let pattern = detect_sparsity(&f, x, eps)?;
620
621 for &(i, j) in &pattern.entries {
623 accumulated_pattern.add_entry(i, j);
624 }
625 }
626
627 Ok(accumulated_pattern)
628}
629
630pub struct BlockPattern {
632 pub block_sizes: Vec<(usize, usize)>,
634 pub blocks: Vec<(usize, usize)>,
636 pub nrows: usize,
638 pub n_cols: usize,
639}
640
641impl BlockPattern {
642 pub fn to_sparse_pattern(&self) -> SparsePattern {
644 let mut pattern = SparsePattern::new(self.nrows, self.n_cols);
645
646 let mut row_offset = 0;
647 let mut col_offset = 0;
648
649 for &(blockrow, block_col) in &self.blocks {
650 let (block_height, block_width) = self.block_sizes[blockrow];
651
652 for i in 0..block_height {
654 for j in 0..block_width {
655 pattern.add_entry(row_offset + i, col_offset + j);
656 }
657 }
658
659 col_offset += block_width;
660 if block_col == self.blocks.len() - 1 {
661 col_offset = 0;
662 row_offset += block_height;
663 }
664 }
665
666 pattern
667 }
668}
669
670pub struct HybridJacobian<F: IntegrateFloat> {
672 pub sparse_blocks: Vec<CSRJacobian<F>>,
674 pub dense_blocks: Vec<Array2<F>>,
676 pub block_info: BlockPattern,
678}
679
680#[cfg(test)]
681mod tests {
682 use super::*;
683 use crate::{SparseJacobian, SparsePattern};
684 use scirs2_core::ndarray::Array1;
685
686 #[test]
687 fn test_sparse_pattern() {
688 let mut pattern = SparsePattern::new(3, 3);
689 pattern.add_entry(0, 0);
690 pattern.add_entry(1, 1);
691 pattern.add_entry(2, 2);
692 pattern.add_entry(0, 1);
693
694 assert_eq!(pattern.nnz(), 4);
695 assert!(pattern.sparsity() > 0.5);
696 }
697
698 #[test]
699 fn test_coloring() {
700 let pattern = example_tridiagonal_pattern(5);
701 let coloring = pattern.compute_coloring();
702
703 assert!(coloring.n_groups() <= 3);
705 }
706
707 #[test]
708 fn test_sparse_jacobian() {
709 let pattern = example_tridiagonal_pattern(3);
710 let mut jac = SparseJacobian::from_pattern(pattern);
711
712 let _ = jac.set(0, 0, 2.0);
714 let _ = jac.set(0, 1, -1.0);
715 let _ = jac.set(1, 0, -1.0);
716 let _ = jac.set(1, 1, 2.0);
717 let _ = jac.set(1, 2, -1.0);
718 let _ = jac.set(2, 1, -1.0);
719 let _ = jac.set(2, 2, 2.0);
720
721 let x = Array1::from_vec(vec![1.0, 2.0, 3.0]);
723 let y = jac.apply(x.view()).unwrap();
724
725 assert!((y[0] - 0.0_f64).abs() < 1e-10);
727 assert!((y[1] - 0.0_f64).abs() < 1e-10);
728 assert!((y[2] - 4.0_f64).abs() < 1e-10);
729 }
730
731 #[test]
732 fn test_csr_format() {
733 let pattern = example_tridiagonal_pattern(3);
734 let mut jac = SparseJacobian::from_pattern(pattern);
735
736 let _ = jac.set(0, 0, 2.0);
738 let _ = jac.set(0, 1, -1.0);
739 let _ = jac.set(1, 0, -1.0);
740 let _ = jac.set(1, 1, 2.0);
741 let _ = jac.set(1, 2, -1.0);
742 let _ = jac.set(2, 1, -1.0);
743 let _ = jac.set(2, 2, 2.0);
744
745 let csr = jac.to_csr();
747
748 let x = Array1::from_vec(vec![1.0, 2.0, 3.0]);
750 let y = csr.apply(x.view()).unwrap();
751
752 assert!((y[0] - 0.0_f64).abs() < 1e-10);
753 assert!((y[1] - 0.0_f64).abs() < 1e-10);
754 assert!((y[2] - 4.0_f64).abs() < 1e-10);
755 }
756}