1use std::ffi::c_int;
5use std::os::raw::c_void;
6use std::ptr;
7
8use crate::csc::{CscMatrix, try_usize_to_i32, validate_csc_pattern};
9use crate::error::{SparseError, SparseResult};
10
11const STRICT_RCOND_THRESHOLD: f64 = 1e-12;
12
13#[repr(C)]
14struct KluCommon {
15 tol: f64,
16 memgrow: f64,
17 initmem_amd: f64,
18 initmem: f64,
19 maxwork: f64,
20 btf: c_int,
21 ordering: c_int,
22 scale: c_int,
23 user_order:
24 Option<unsafe extern "C" fn(i32, *mut i32, *mut i32, *mut i32, *mut KluCommon) -> i32>,
25 user_data: *mut c_void,
26 halt_if_singular: c_int,
27 status: c_int,
28 nrealloc: c_int,
29 structural_rank: i32,
30 numerical_rank: i32,
31 singular_col: i32,
32 noffdiag: i32,
33 flops: f64,
34 rcond: f64,
35 condest: f64,
36 rgrowth: f64,
37 work: f64,
38 memusage: usize,
39 mempeak: usize,
40}
41
42enum KluSymbolic {}
43enum KluNumeric {}
44
45unsafe extern "C" {
46 fn klu_defaults(common: *mut KluCommon) -> c_int;
47 fn klu_analyze(
48 n: i32,
49 ap: *const i32,
50 ai: *const i32,
51 common: *mut KluCommon,
52 ) -> *mut KluSymbolic;
53 fn klu_factor(
54 ap: *const i32,
55 ai: *const i32,
56 ax: *const f64,
57 symbolic: *mut KluSymbolic,
58 common: *mut KluCommon,
59 ) -> *mut KluNumeric;
60 fn klu_refactor(
61 ap: *const i32,
62 ai: *const i32,
63 ax: *const f64,
64 symbolic: *mut KluSymbolic,
65 numeric: *mut KluNumeric,
66 common: *mut KluCommon,
67 ) -> c_int;
68 fn klu_solve(
69 symbolic: *mut KluSymbolic,
70 numeric: *mut KluNumeric,
71 ldim: i32,
72 nrhs: i32,
73 b: *mut f64,
74 common: *mut KluCommon,
75 ) -> c_int;
76 fn klu_tsolve(
77 symbolic: *mut KluSymbolic,
78 numeric: *mut KluNumeric,
79 ldim: i32,
80 nrhs: i32,
81 b: *mut f64,
82 common: *mut KluCommon,
83 ) -> c_int;
84 fn klu_free_symbolic(symbolic: *mut *mut KluSymbolic, common: *mut KluCommon) -> c_int;
85 fn klu_free_numeric(numeric: *mut *mut KluNumeric, common: *mut KluCommon) -> c_int;
86 fn klu_rcond(
87 symbolic: *mut KluSymbolic,
88 numeric: *mut KluNumeric,
89 common: *mut KluCommon,
90 ) -> c_int;
91}
92
93pub struct KluSolver {
95 common: KluCommon,
96 symbolic: *mut KluSymbolic,
97 numeric: *mut KluNumeric,
98 dim: i32,
99 nnz: usize,
100 col_ptrs: Vec<i32>,
101 row_indices: Vec<i32>,
102}
103
104unsafe impl Send for KluSolver {}
105
106impl KluSolver {
107 pub fn new(dim: usize, col_ptrs: &[usize], row_indices: &[usize]) -> SparseResult<Self> {
109 validate_csc_pattern(dim, dim, col_ptrs, row_indices)?;
110 Self::analyze(dim, col_ptrs, row_indices)
111 }
112
113 pub fn from_csc<T>(matrix: &CscMatrix<T>) -> SparseResult<Self> {
115 if !matrix.is_square() {
116 return Err(SparseError::MatrixNotSquare {
117 nrows: matrix.nrows(),
118 ncols: matrix.ncols(),
119 });
120 }
121 Self::analyze(matrix.nrows(), matrix.col_ptrs(), matrix.row_indices())
122 }
123
124 fn analyze(dim: usize, col_ptrs: &[usize], row_indices: &[usize]) -> SparseResult<Self> {
125 if dim == 0 {
126 return Err(SparseError::EmptyMatrix);
127 }
128
129 let dim = try_usize_to_i32("matrix dimension", dim)?;
130 let col_ptrs = col_ptrs
131 .iter()
132 .copied()
133 .map(|value| try_usize_to_i32("column pointer", value))
134 .collect::<SparseResult<Vec<_>>>()?;
135 let row_indices = row_indices
136 .iter()
137 .copied()
138 .map(|value| try_usize_to_i32("row index", value))
139 .collect::<SparseResult<Vec<_>>>()?;
140
141 let mut common = unsafe { std::mem::zeroed() };
142 unsafe {
143 klu_defaults(&mut common);
144 }
145
146 let symbolic =
147 unsafe { klu_analyze(dim, col_ptrs.as_ptr(), row_indices.as_ptr(), &mut common) };
148 if symbolic.is_null() {
149 return Err(SparseError::KluAnalyzeFailed);
150 }
151
152 Ok(Self {
153 common,
154 symbolic,
155 numeric: ptr::null_mut(),
156 dim,
157 nnz: row_indices.len(),
158 col_ptrs,
159 row_indices,
160 })
161 }
162
163 fn clear_numeric(&mut self) {
164 if !self.numeric.is_null() {
165 unsafe {
166 klu_free_numeric(&mut self.numeric, &mut self.common);
167 }
168 }
169 }
170
171 pub fn factor(&mut self, values: &[f64]) -> SparseResult<()> {
172 self.validate_values(values)?;
173
174 self.clear_numeric();
175
176 self.numeric = unsafe {
177 klu_factor(
178 self.col_ptrs.as_ptr(),
179 self.row_indices.as_ptr(),
180 values.as_ptr(),
181 self.symbolic,
182 &mut self.common,
183 )
184 };
185 if self.numeric.is_null() {
186 return Err(SparseError::KluFactorFailed);
187 }
188
189 if let Err(error) = self.finish_factorization(SparseError::KluFactorFailed) {
190 self.clear_numeric();
191 return Err(error);
192 }
193 Ok(())
194 }
195
196 pub fn refactor(&mut self, values: &[f64]) -> SparseResult<()> {
197 self.validate_values(values)?;
198 if self.numeric.is_null() {
199 return Err(SparseError::NotFactorized);
200 }
201
202 let ok = unsafe {
203 klu_refactor(
204 self.col_ptrs.as_ptr(),
205 self.row_indices.as_ptr(),
206 values.as_ptr(),
207 self.symbolic,
208 self.numeric,
209 &mut self.common,
210 )
211 };
212 if ok == 0 {
213 self.clear_numeric();
214 return Err(SparseError::KluRefactorFailed);
215 }
216
217 if let Err(error) = self.finish_factorization(SparseError::KluRefactorFailed) {
218 self.clear_numeric();
219 return Err(error);
220 }
221 Ok(())
222 }
223
224 pub fn solve(&mut self, rhs: &mut [f64]) -> SparseResult<()> {
225 self.validate_rhs(rhs)?;
226
227 let ok = unsafe {
228 klu_solve(
229 self.symbolic,
230 self.numeric,
231 self.dim,
232 1,
233 rhs.as_mut_ptr(),
234 &mut self.common,
235 )
236 };
237 if ok == 0 {
238 return Err(SparseError::KluSolveFailed);
239 }
240 Ok(())
241 }
242
243 pub fn solve_transpose(&mut self, rhs: &mut [f64]) -> SparseResult<()> {
244 self.validate_rhs(rhs)?;
245
246 let ok = unsafe {
247 klu_tsolve(
248 self.symbolic,
249 self.numeric,
250 self.dim,
251 1,
252 rhs.as_mut_ptr(),
253 &mut self.common,
254 )
255 };
256 if ok == 0 {
257 return Err(SparseError::KluSolveFailed);
258 }
259 Ok(())
260 }
261
262 pub fn rcond(&self) -> f64 {
263 self.common.rcond
264 }
265
266 fn validate_values(&self, values: &[f64]) -> SparseResult<()> {
267 if values.len() != self.nnz {
268 return Err(SparseError::ValueCountMismatch {
269 expected: self.nnz,
270 found: values.len(),
271 });
272 }
273 Ok(())
274 }
275
276 fn validate_rhs(&self, rhs: &[f64]) -> SparseResult<()> {
277 if self.numeric.is_null() {
278 return Err(SparseError::NotFactorized);
279 }
280 if rhs.len() != self.dim as usize {
281 return Err(SparseError::RhsLengthMismatch {
282 expected: self.dim as usize,
283 found: rhs.len(),
284 });
285 }
286 Ok(())
287 }
288
289 fn finish_factorization(&mut self, ill_conditioned_error: SparseError) -> SparseResult<()> {
290 let ok = unsafe { klu_rcond(self.symbolic, self.numeric, &mut self.common) };
291 if ok == 0 {
292 return Err(SparseError::KluRcondFailed);
293 }
294 let rcond = self.common.rcond;
295 if !rcond.is_finite() || rcond < STRICT_RCOND_THRESHOLD {
296 return Err(match ill_conditioned_error {
297 SparseError::KluFactorFailed => SparseError::KluIllConditioned {
298 rcond,
299 threshold: STRICT_RCOND_THRESHOLD,
300 },
301 SparseError::KluRefactorFailed => SparseError::KluIllConditioned {
302 rcond,
303 threshold: STRICT_RCOND_THRESHOLD,
304 },
305 other => other,
306 });
307 }
308 Ok(())
309 }
310
311 pub fn solve_many(&mut self, rhs: &mut [f64], nrhs: usize) -> SparseResult<()> {
316 if self.numeric.is_null() {
317 return Err(SparseError::NotFactorized);
318 }
319 let expected = self.dim as usize * nrhs;
320 if rhs.len() != expected {
321 return Err(SparseError::RhsLengthMismatch {
322 expected,
323 found: rhs.len(),
324 });
325 }
326 if nrhs == 0 {
327 return Ok(());
328 }
329 let nrhs_i32 = try_usize_to_i32("nrhs", nrhs)?;
330 let ok = unsafe {
331 klu_solve(
332 self.symbolic,
333 self.numeric,
334 self.dim,
335 nrhs_i32,
336 rhs.as_mut_ptr(),
337 &mut self.common,
338 )
339 };
340 if ok == 0 {
341 return Err(SparseError::KluSolveFailed);
342 }
343 Ok(())
344 }
345}
346
347impl Drop for KluSolver {
348 fn drop(&mut self) {
349 unsafe {
350 if !self.numeric.is_null() {
351 klu_free_numeric(&mut self.numeric, &mut self.common);
352 }
353 if !self.symbolic.is_null() {
354 klu_free_symbolic(&mut self.symbolic, &mut self.common);
355 }
356 }
357 }
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363 use crate::{CscMatrix, Triplet};
364
365 #[test]
367 fn klu_identity_2x2() {
368 let mat = CscMatrix::try_from_triplets(
369 2,
370 2,
371 &[
372 Triplet {
373 row: 0,
374 col: 0,
375 val: 1.0,
376 },
377 Triplet {
378 row: 1,
379 col: 1,
380 val: 1.0,
381 },
382 ],
383 )
384 .unwrap();
385
386 let mut solver = KluSolver::from_csc(&mat).unwrap();
387 solver.factor(mat.values()).unwrap();
388
389 let mut rhs = vec![3.0, 7.0];
390 solver.solve(&mut rhs).unwrap();
391 assert!((rhs[0] - 3.0).abs() < 1e-14);
392 assert!((rhs[1] - 7.0).abs() < 1e-14);
393 }
394
395 #[test]
397 fn klu_lower_triangular() {
398 let triplets = vec![
399 Triplet {
400 row: 0,
401 col: 0,
402 val: 2.0,
403 },
404 Triplet {
405 row: 1,
406 col: 0,
407 val: 1.0,
408 },
409 Triplet {
410 row: 1,
411 col: 1,
412 val: 3.0,
413 },
414 Triplet {
415 row: 2,
416 col: 1,
417 val: 4.0,
418 },
419 Triplet {
420 row: 2,
421 col: 2,
422 val: 5.0,
423 },
424 ];
425 let mat = CscMatrix::try_from_triplets(3, 3, &triplets).unwrap();
426 let mut solver = KluSolver::from_csc(&mat).unwrap();
427 solver.factor(mat.values()).unwrap();
428
429 let x_exact = [1.0, 2.0, 3.0];
431 let mut rhs = vec![2.0 * 1.0, 1.0 * 1.0 + 3.0 * 2.0, 4.0 * 2.0 + 5.0 * 3.0];
432 solver.solve(&mut rhs).unwrap();
433 for i in 0..3 {
434 assert!(
435 (rhs[i] - x_exact[i]).abs() < 1e-12,
436 "x[{i}] = {} expected {}",
437 rhs[i],
438 x_exact[i],
439 );
440 }
441 }
442
443 #[test]
445 fn klu_sparse_4x4() {
446 let triplets = vec![
448 Triplet {
449 row: 0,
450 col: 0,
451 val: 4.0,
452 },
453 Triplet {
454 row: 1,
455 col: 0,
456 val: -1.0,
457 },
458 Triplet {
459 row: 0,
460 col: 1,
461 val: -1.0,
462 },
463 Triplet {
464 row: 1,
465 col: 1,
466 val: 4.0,
467 },
468 Triplet {
469 row: 2,
470 col: 1,
471 val: -1.0,
472 },
473 Triplet {
474 row: 1,
475 col: 2,
476 val: -1.0,
477 },
478 Triplet {
479 row: 2,
480 col: 2,
481 val: 4.0,
482 },
483 Triplet {
484 row: 3,
485 col: 2,
486 val: -1.0,
487 },
488 Triplet {
489 row: 2,
490 col: 3,
491 val: -1.0,
492 },
493 Triplet {
494 row: 3,
495 col: 3,
496 val: 4.0,
497 },
498 ];
499 let mat = CscMatrix::try_from_triplets(4, 4, &triplets).unwrap();
500 let mut solver = KluSolver::from_csc(&mat).unwrap();
501 solver.factor(mat.values()).unwrap();
502
503 let mut rhs = vec![1.0, 0.0, 0.0, 1.0];
504 solver.solve(&mut rhs).unwrap();
505
506 let b_orig = [1.0, 0.0, 0.0, 1.0];
508 let ax = [
509 4.0 * rhs[0] - rhs[1],
510 -rhs[0] + 4.0 * rhs[1] - rhs[2],
511 -rhs[1] + 4.0 * rhs[2] - rhs[3],
512 -rhs[2] + 4.0 * rhs[3],
513 ];
514 for i in 0..4 {
515 assert!(
516 (ax[i] - b_orig[i]).abs() < 1e-12,
517 "residual[{i}] = {:.2e}",
518 (ax[i] - b_orig[i]).abs(),
519 );
520 }
521 }
522
523 #[test]
525 fn klu_refactor_same_pattern() {
526 let triplets = vec![
527 Triplet {
528 row: 0,
529 col: 0,
530 val: 2.0,
531 },
532 Triplet {
533 row: 1,
534 col: 1,
535 val: 3.0,
536 },
537 ];
538 let mat = CscMatrix::try_from_triplets(2, 2, &triplets).unwrap();
539 let mut solver = KluSolver::from_csc(&mat).unwrap();
540 solver.factor(mat.values()).unwrap();
541
542 let mut rhs = vec![4.0, 9.0];
543 solver.solve(&mut rhs).unwrap();
544 assert!((rhs[0] - 2.0).abs() < 1e-14);
545 assert!((rhs[1] - 3.0).abs() < 1e-14);
546
547 solver.refactor(&[5.0, 10.0]).unwrap();
549 let mut rhs2 = vec![15.0, 30.0];
550 solver.solve(&mut rhs2).unwrap();
551 assert!((rhs2[0] - 3.0).abs() < 1e-14);
552 assert!((rhs2[1] - 3.0).abs() < 1e-14);
553 }
554
555 #[test]
557 fn klu_transpose_solve() {
558 let triplets = vec![
559 Triplet {
560 row: 0,
561 col: 0,
562 val: 1.0,
563 },
564 Triplet {
565 row: 1,
566 col: 0,
567 val: 2.0,
568 },
569 Triplet {
570 row: 1,
571 col: 1,
572 val: 3.0,
573 },
574 ];
575 let mat = CscMatrix::try_from_triplets(2, 2, &triplets).unwrap();
576 let mut solver = KluSolver::from_csc(&mat).unwrap();
577 solver.factor(mat.values()).unwrap();
578
579 let mut rhs = vec![5.0, 6.0];
581 solver.solve_transpose(&mut rhs).unwrap();
582 assert!((rhs[0] - 1.0).abs() < 1e-14);
583 assert!((rhs[1] - 2.0).abs() < 1e-14);
584 }
585
586 #[test]
588 fn klu_solve_many() {
589 let triplets = vec![
590 Triplet {
591 row: 0,
592 col: 0,
593 val: 2.0,
594 },
595 Triplet {
596 row: 1,
597 col: 1,
598 val: 5.0,
599 },
600 ];
601 let mat = CscMatrix::try_from_triplets(2, 2, &triplets).unwrap();
602 let mut solver = KluSolver::from_csc(&mat).unwrap();
603 solver.factor(mat.values()).unwrap();
604
605 let mut rhs = vec![4.0, 10.0, 6.0, 15.0];
607 solver.solve_many(&mut rhs, 2).unwrap();
608 assert!((rhs[0] - 2.0).abs() < 1e-14); assert!((rhs[1] - 2.0).abs() < 1e-14); assert!((rhs[2] - 3.0).abs() < 1e-14); assert!((rhs[3] - 3.0).abs() < 1e-14); }
613
614 #[test]
616 fn klu_solve_before_factor_errors() {
617 let triplets = vec![
618 Triplet {
619 row: 0,
620 col: 0,
621 val: 1.0,
622 },
623 Triplet {
624 row: 1,
625 col: 1,
626 val: 1.0,
627 },
628 ];
629 let mat = CscMatrix::try_from_triplets(2, 2, &triplets).unwrap();
630 let mut solver = KluSolver::from_csc(&mat).unwrap();
631
632 let mut rhs = vec![1.0, 2.0];
633 let err = solver.solve(&mut rhs).unwrap_err();
634 assert!(matches!(err, SparseError::NotFactorized));
635 }
636
637 #[test]
639 fn klu_rhs_length_mismatch() {
640 let triplets = vec![
641 Triplet {
642 row: 0,
643 col: 0,
644 val: 1.0,
645 },
646 Triplet {
647 row: 1,
648 col: 1,
649 val: 1.0,
650 },
651 ];
652 let mat = CscMatrix::try_from_triplets(2, 2, &triplets).unwrap();
653 let mut solver = KluSolver::from_csc(&mat).unwrap();
654 solver.factor(mat.values()).unwrap();
655
656 let mut rhs = vec![1.0, 2.0, 3.0]; let err = solver.solve(&mut rhs).unwrap_err();
658 assert!(matches!(err, SparseError::RhsLengthMismatch { .. }));
659 }
660
661 #[test]
663 fn csc_triplet_duplicates_summed() {
664 let triplets = vec![
665 Triplet {
666 row: 0,
667 col: 0,
668 val: 1.0,
669 },
670 Triplet {
671 row: 0,
672 col: 0,
673 val: 2.0,
674 },
675 Triplet {
676 row: 1,
677 col: 1,
678 val: 5.0,
679 },
680 ];
681 let mat = CscMatrix::try_from_triplets(2, 2, &triplets).unwrap();
682 assert_eq!(mat.nnz(), 2); assert!((mat.values()[0] - 3.0_f64).abs() < 1e-14); }
685
686 #[test]
688 fn klu_large_identity() {
689 let n = 500;
690 let triplets: Vec<Triplet<f64>> = (0..n)
691 .map(|i| Triplet {
692 row: i,
693 col: i,
694 val: 1.0,
695 })
696 .collect();
697 let mat = CscMatrix::try_from_triplets(n, n, &triplets).unwrap();
698 let mut solver = KluSolver::from_csc(&mat).unwrap();
699 solver.factor(mat.values()).unwrap();
700
701 let mut rhs: Vec<f64> = (0..n).map(|i| i as f64).collect();
702 solver.solve(&mut rhs).unwrap();
703 for (i, val) in rhs.iter().enumerate() {
704 assert!((val - i as f64).abs() < 1e-12);
705 }
706 }
707
708 #[test]
711 fn klu_structurally_singular_empty_column() {
712 let triplets = vec![Triplet {
714 row: 0,
715 col: 0,
716 val: 1.0,
717 }];
718 let mat = CscMatrix::try_from_triplets(2, 2, &triplets).unwrap();
719 let mut solver = KluSolver::from_csc(&mat).unwrap();
720 let result = solver.factor(mat.values());
721 assert!(
722 result.is_err(),
723 "factoring a structurally singular matrix should fail"
724 );
725 }
726
727 #[test]
730 fn klu_numerically_singular_matrix() {
731 let triplets = vec![
732 Triplet {
733 row: 0,
734 col: 0,
735 val: 1.0,
736 },
737 Triplet {
738 row: 1,
739 col: 0,
740 val: 1.0,
741 },
742 Triplet {
743 row: 0,
744 col: 1,
745 val: 1.0,
746 },
747 Triplet {
748 row: 1,
749 col: 1,
750 val: 1.0,
751 },
752 ];
753 let mat = CscMatrix::try_from_triplets(2, 2, &triplets).unwrap();
754 let mut solver = KluSolver::from_csc(&mat).unwrap();
755 let err = solver.factor(mat.values()).unwrap_err();
756 assert!(
757 matches!(
758 err,
759 SparseError::KluIllConditioned { .. } | SparseError::KluFactorFailed
760 ),
761 "singular matrix should be rejected, got {err:?}"
762 );
763 }
764
765 #[test]
767 fn klu_ill_conditioned_rcond() {
768 let triplets = vec![
771 Triplet {
772 row: 0,
773 col: 0,
774 val: 1.0,
775 },
776 Triplet {
777 row: 1,
778 col: 0,
779 val: 0.5,
780 },
781 Triplet {
782 row: 2,
783 col: 0,
784 val: 1.0 / 3.0,
785 },
786 Triplet {
787 row: 0,
788 col: 1,
789 val: 0.5,
790 },
791 Triplet {
792 row: 1,
793 col: 1,
794 val: 1.0 / 3.0,
795 },
796 Triplet {
797 row: 2,
798 col: 1,
799 val: 0.25,
800 },
801 Triplet {
802 row: 0,
803 col: 2,
804 val: 1.0 / 3.0,
805 },
806 Triplet {
807 row: 1,
808 col: 2,
809 val: 0.25,
810 },
811 Triplet {
812 row: 2,
813 col: 2,
814 val: 0.2,
815 },
816 ];
817 let mat = CscMatrix::try_from_triplets(3, 3, &triplets).unwrap();
818 let mut solver = KluSolver::from_csc(&mat).unwrap();
819 solver.factor(mat.values()).unwrap();
820
821 assert!(
823 solver.rcond() < 0.1,
824 "rcond for a 3x3 Hilbert matrix should be small, got {}",
825 solver.rcond()
826 );
827 assert!(solver.rcond() > 0.0, "rcond should still be positive");
828 }
829
830 #[test]
832 fn klu_refactor_with_zero_values() {
833 let triplets = vec![
835 Triplet {
836 row: 0,
837 col: 0,
838 val: 2.0,
839 },
840 Triplet {
841 row: 0,
842 col: 1,
843 val: 1.0,
844 },
845 Triplet {
846 row: 1,
847 col: 0,
848 val: 1.0,
849 },
850 Triplet {
851 row: 1,
852 col: 1,
853 val: 3.0,
854 },
855 ];
856 let mat = CscMatrix::try_from_triplets(2, 2, &triplets).unwrap();
857 let mut solver = KluSolver::from_csc(&mat).unwrap();
858 solver.factor(mat.values()).unwrap();
859
860 let mut rhs = vec![3.0, 4.0];
862 solver.solve(&mut rhs).unwrap();
863
864 let singular_vals: Vec<f64> = vec![1.0, 1.0, 1.0, 1.0]; let err = solver.refactor(&singular_vals).unwrap_err();
868 assert!(
869 matches!(
870 err,
871 SparseError::KluIllConditioned { .. } | SparseError::KluRefactorFailed
872 ),
873 "refactoring to singular values should be rejected, got {err:?}"
874 );
875
876 let mut stale_rhs = vec![3.0, 4.0];
877 let solve_err = solver.solve(&mut stale_rhs).unwrap_err();
878 assert!(
879 matches!(solve_err, SparseError::NotFactorized),
880 "failed refactor must invalidate numeric factors, got {solve_err:?}"
881 );
882 }
883
884 #[test]
886 fn klu_solve_many_non_diagonal() {
887 let triplets = vec![
889 Triplet {
890 row: 0,
891 col: 0,
892 val: 2.0,
893 },
894 Triplet {
895 row: 1,
896 col: 0,
897 val: 1.0,
898 },
899 Triplet {
900 row: 0,
901 col: 1,
902 val: 1.0,
903 },
904 Triplet {
905 row: 1,
906 col: 1,
907 val: 3.0,
908 },
909 ];
910 let mat = CscMatrix::try_from_triplets(2, 2, &triplets).unwrap();
911 let mut solver = KluSolver::from_csc(&mat).unwrap();
912 solver.factor(mat.values()).unwrap();
913
914 let mut rhs = vec![2.0, 1.0, 1.0, 3.0, 3.0, 4.0];
917 solver.solve_many(&mut rhs, 3).unwrap();
918 assert!((rhs[0] - 1.0).abs() < 1e-12, "col1[0]={}", rhs[0]);
919 assert!((rhs[1] - 0.0).abs() < 1e-12, "col1[1]={}", rhs[1]);
920 assert!((rhs[2] - 0.0).abs() < 1e-12, "col2[0]={}", rhs[2]);
921 assert!((rhs[3] - 1.0).abs() < 1e-12, "col2[1]={}", rhs[3]);
922 assert!((rhs[4] - 1.0).abs() < 1e-12, "col3[0]={}", rhs[4]);
923 assert!((rhs[5] - 1.0).abs() < 1e-12, "col3[1]={}", rhs[5]);
924 }
925
926 #[test]
928 fn klu_solve_many_zero_columns() {
929 let triplets = vec![
930 Triplet {
931 row: 0,
932 col: 0,
933 val: 1.0,
934 },
935 Triplet {
936 row: 1,
937 col: 1,
938 val: 1.0,
939 },
940 ];
941 let mat = CscMatrix::try_from_triplets(2, 2, &triplets).unwrap();
942 let mut solver = KluSolver::from_csc(&mat).unwrap();
943 solver.factor(mat.values()).unwrap();
944
945 let mut rhs = vec![];
946 solver.solve_many(&mut rhs, 0).unwrap();
947 assert!(rhs.is_empty());
948 }
949
950 #[test]
952 fn klu_solve_many_wrong_length() {
953 let triplets = vec![
954 Triplet {
955 row: 0,
956 col: 0,
957 val: 1.0,
958 },
959 Triplet {
960 row: 1,
961 col: 1,
962 val: 1.0,
963 },
964 ];
965 let mat = CscMatrix::try_from_triplets(2, 2, &triplets).unwrap();
966 let mut solver = KluSolver::from_csc(&mat).unwrap();
967 solver.factor(mat.values()).unwrap();
968
969 let mut rhs = vec![1.0, 2.0, 3.0]; let err = solver.solve_many(&mut rhs, 2).unwrap_err();
971 assert!(matches!(err, SparseError::RhsLengthMismatch { .. }));
972 }
973
974 #[test]
976 fn klu_factor_wrong_value_count() {
977 let triplets = vec![
978 Triplet {
979 row: 0,
980 col: 0,
981 val: 1.0,
982 },
983 Triplet {
984 row: 1,
985 col: 1,
986 val: 1.0,
987 },
988 ];
989 let mat = CscMatrix::try_from_triplets(2, 2, &triplets).unwrap();
990 let mut solver = KluSolver::from_csc(&mat).unwrap();
991
992 let err = solver.factor(&[1.0]).unwrap_err(); assert!(matches!(err, SparseError::ValueCountMismatch { .. }));
994 }
995
996 #[test]
998 fn klu_refactor_before_factor() {
999 let triplets = vec![
1000 Triplet {
1001 row: 0,
1002 col: 0,
1003 val: 1.0,
1004 },
1005 Triplet {
1006 row: 1,
1007 col: 1,
1008 val: 1.0,
1009 },
1010 ];
1011 let mat = CscMatrix::try_from_triplets(2, 2, &triplets).unwrap();
1012 let mut solver = KluSolver::from_csc(&mat).unwrap();
1013
1014 let err = solver.refactor(&[2.0, 3.0]).unwrap_err();
1015 assert!(matches!(err, SparseError::NotFactorized));
1016 }
1017
1018 #[test]
1020 fn klu_rejects_non_square_from_csc() {
1021 let mat =
1022 CscMatrix::try_new(2, 3, vec![0, 1, 2, 3], vec![0, 1, 0], vec![1.0, 2.0, 3.0]).unwrap();
1023 let result = KluSolver::from_csc(&mat);
1024 assert!(matches!(result, Err(SparseError::MatrixNotSquare { .. })));
1025 }
1026
1027 #[test]
1029 fn klu_rejects_empty_matrix() {
1030 let mat = CscMatrix::<f64>::try_new(0, 0, vec![0], vec![], vec![]).unwrap();
1031 let result = KluSolver::from_csc(&mat);
1032 assert!(matches!(result, Err(SparseError::EmptyMatrix)));
1033 }
1034}