1use super::explicit_schur::{SchurBlockStructure, SchurPreconditioner};
54use crate::core::VarKey;
55use crate::core::variable::ManifoldVariable;
56use crate::linalg::{LinAlgError, LinAlgResult, LinearSolver, SparseMode, StructureAware};
57use faer::Mat;
58use faer::sparse::{SparseColMat, Triplet};
59use nalgebra::{DMatrix, DVector, Matrix3};
60use rayon::prelude::*;
61use slotmap::{SecondaryMap, SlotMap};
62use std::collections::HashMap;
63use std::ops::Mul;
64
65#[derive(Debug, Clone)]
67pub struct IterativeSchurSolver {
68 block_structure: Option<SchurBlockStructure>,
69
70 max_cg_iterations: usize,
72 cg_tolerance: f64,
73
74 preconditioner_type: SchurPreconditioner,
76
77 landmark_block_inverses: Vec<Matrix3<f64>>,
79 hessian: Option<SparseColMat<usize, f64>>,
80 gradient: Option<Mat<f64>>,
81
82 workspace_lm: Vec<f64>, workspace_cam: Vec<f64>, camera_to_landmark_visibility: Vec<Vec<usize>>,
89}
90
91impl IterativeSchurSolver {
92 pub fn new() -> Self {
96 Self {
97 block_structure: None,
98
99 max_cg_iterations: 500, cg_tolerance: 1e-9, preconditioner_type: SchurPreconditioner::SchurJacobi,
102 landmark_block_inverses: Vec::new(),
103 hessian: None,
104 gradient: None,
105 workspace_lm: Vec::new(),
106 workspace_cam: Vec::new(),
107 camera_to_landmark_visibility: Vec::new(),
108 }
109 }
110
111 pub fn with_cg_params(max_iterations: usize, tolerance: f64) -> Self {
113 Self {
114 block_structure: None,
115
116 max_cg_iterations: max_iterations,
117 cg_tolerance: tolerance,
118 preconditioner_type: SchurPreconditioner::SchurJacobi,
119 landmark_block_inverses: Vec::new(),
120 hessian: None,
121 gradient: None,
122 workspace_lm: Vec::new(),
123 workspace_cam: Vec::new(),
124 camera_to_landmark_visibility: Vec::new(),
125 }
126 }
127
128 pub fn with_config(
130 max_iterations: usize,
131 tolerance: f64,
132 preconditioner: SchurPreconditioner,
133 ) -> Self {
134 Self {
135 block_structure: None,
136
137 max_cg_iterations: max_iterations,
138 cg_tolerance: tolerance,
139 preconditioner_type: preconditioner,
140 landmark_block_inverses: Vec::new(),
141 hessian: None,
142 gradient: None,
143 workspace_lm: Vec::new(),
144 workspace_cam: Vec::new(),
145 camera_to_landmark_visibility: Vec::new(),
146 }
147 }
148
149 fn init_workspaces(&mut self) {
151 if let Some(structure) = &self.block_structure {
152 let lm_dof = structure.landmark_dof;
153 let cam_dof = structure.camera_dof;
154
155 if self.workspace_lm.len() != lm_dof {
156 self.workspace_lm = vec![0.0; lm_dof];
157 }
158 if self.workspace_cam.len() != cam_dof {
159 self.workspace_cam = vec![0.0; cam_dof];
160 }
161 }
162 }
163
164 fn apply_schur_operator_fast(
169 &self,
170 x: &Mat<f64>,
171 result: &mut Mat<f64>,
172 temp_lm: &mut [f64],
173 temp_cam: &mut [f64],
174 ) -> LinAlgResult<()> {
175 let structure = self
176 .block_structure
177 .as_ref()
178 .ok_or_else(|| LinAlgError::InvalidInput("Block structure not initialized".into()))?;
179
180 let hessian = self
181 .hessian
182 .as_ref()
183 .ok_or_else(|| LinAlgError::InvalidInput("Hessian not computed".into()))?;
184
185 let symbolic = hessian.symbolic();
186 let (cam_start, cam_end) = structure.camera_col_range();
187 let (lm_start, lm_end) = structure.landmark_col_range();
188 let cam_dof = structure.camera_dof;
189
190 temp_lm.iter_mut().for_each(|v| *v = 0.0);
192 temp_cam.iter_mut().for_each(|v| *v = 0.0);
193
194 for col in cam_start..cam_end {
197 let local_col = col - cam_start;
198 let x_val = x[(local_col, 0)];
199 let row_indices = symbolic.row_idx_of_col_raw(col);
200 let col_values = hessian.val_of_col(col);
201
202 for (idx, &row) in row_indices.iter().enumerate() {
203 let val = col_values[idx];
204 if row >= cam_start && row < cam_end {
205 let local_row = row - cam_start;
207 result[(local_row, 0)] += val * x_val;
208 } else if row >= lm_start && row < lm_end {
209 let local_row = row - lm_start;
211 temp_lm[local_row] += val * x_val;
212 }
213 }
214 }
215
216 for (block_idx, (_, start_col, _)) in structure.landmark_blocks.iter().enumerate() {
218 let inv_block = &self.landmark_block_inverses[block_idx];
219 let local_start = start_col - lm_start;
220
221 let in0 = temp_lm[local_start];
223 let in1 = temp_lm[local_start + 1];
224 let in2 = temp_lm[local_start + 2];
225
226 temp_lm[local_start] =
228 inv_block[(0, 0)] * in0 + inv_block[(0, 1)] * in1 + inv_block[(0, 2)] * in2;
229 temp_lm[local_start + 1] =
230 inv_block[(1, 0)] * in0 + inv_block[(1, 1)] * in1 + inv_block[(1, 2)] * in2;
231 temp_lm[local_start + 2] =
232 inv_block[(2, 0)] * in0 + inv_block[(2, 1)] * in1 + inv_block[(2, 2)] * in2;
233 }
234
235 for col in lm_start..lm_end {
237 let local_col = col - lm_start;
238 let lm_val = temp_lm[local_col];
239 let row_indices = symbolic.row_idx_of_col_raw(col);
240 let col_values = hessian.val_of_col(col);
241
242 for (idx, &row) in row_indices.iter().enumerate() {
243 if row >= cam_start && row < cam_end {
244 let local_row = row - cam_start;
245 temp_cam[local_row] += col_values[idx] * lm_val;
246 }
247 }
248 }
249
250 for i in 0..cam_dof {
252 result[(i, 0)] -= temp_cam[i];
253 }
254
255 Ok(())
256 }
257
258 fn extract_camera_landmark_transpose_mvp(
260 &self,
261 hessian: &SparseColMat<usize, f64>,
262 x: &Mat<f64>,
263 ) -> LinAlgResult<Mat<f64>> {
264 let structure = self
265 .block_structure
266 .as_ref()
267 .ok_or_else(|| LinAlgError::InvalidInput("Block structure not initialized".into()))?;
268 let (cam_start, cam_end) = structure.camera_col_range();
269 let (lm_start, lm_end) = structure.landmark_col_range();
270 let lm_dof = structure.landmark_dof;
271
272 let mut result = Mat::<f64>::zeros(lm_dof, 1);
273 let symbolic = hessian.symbolic();
274
275 for col in cam_start..cam_end {
277 let local_col = col - cam_start;
278 let row_indices = symbolic.row_idx_of_col_raw(col);
279 let col_values = hessian.val_of_col(col);
280
281 for (idx, &row) in row_indices.iter().enumerate() {
282 if row >= lm_start && row < lm_end {
283 let local_row = row - lm_start;
284 result[(local_row, 0)] += col_values[idx] * x[(local_col, 0)];
285 }
286 }
287 }
288
289 Ok(result)
290 }
291
292 fn extract_camera_landmark_mvp(
294 &self,
295 hessian: &SparseColMat<usize, f64>,
296 x: &Mat<f64>,
297 ) -> LinAlgResult<Mat<f64>> {
298 let structure = self
299 .block_structure
300 .as_ref()
301 .ok_or_else(|| LinAlgError::InvalidInput("Block structure not initialized".into()))?;
302 let (cam_start, cam_end) = structure.camera_col_range();
303 let (lm_start, lm_end) = structure.landmark_col_range();
304 let cam_dof = structure.camera_dof;
305
306 let mut result = Mat::<f64>::zeros(cam_dof, 1);
307 let symbolic = hessian.symbolic();
308
309 for col in lm_start..lm_end {
311 let local_col = col - lm_start;
312 let row_indices = symbolic.row_idx_of_col_raw(col);
313 let col_values = hessian.val_of_col(col);
314
315 for (idx, &row) in row_indices.iter().enumerate() {
316 if row >= cam_start && row < cam_end {
317 let local_row = row - cam_start;
318 result[(local_row, 0)] += col_values[idx] * x[(local_col, 0)];
319 }
320 }
321 }
322
323 Ok(result)
324 }
325
326 fn apply_landmark_inverse(&self, input: &Mat<f64>, output: &mut Mat<f64>) -> LinAlgResult<()> {
328 let structure = self
329 .block_structure
330 .as_ref()
331 .ok_or_else(|| LinAlgError::InvalidInput("Block structure not initialized".into()))?;
332
333 for (block_idx, (_, start_col, _)) in structure.landmark_blocks.iter().enumerate() {
334 let inv_block = &self.landmark_block_inverses[block_idx];
335 let local_start = start_col - structure.landmark_col_range().0;
336
337 for i in 0..3 {
338 let mut sum = 0.0;
339 for j in 0..3 {
340 sum += inv_block[(i, j)] * input[(local_start + j, 0)];
341 }
342 output[(local_start + i, 0)] = sum;
343 }
344 }
345
346 Ok(())
347 }
348
349 fn compute_block_preconditioner(&self) -> LinAlgResult<Vec<DMatrix<f64>>> {
358 let structure = self
359 .block_structure
360 .as_ref()
361 .ok_or_else(|| LinAlgError::InvalidInput("Block structure not initialized".into()))?;
362
363 let hessian = self
364 .hessian
365 .as_ref()
366 .ok_or_else(|| LinAlgError::InvalidInput("Hessian not computed".into()))?;
367
368 let symbolic = hessian.symbolic();
369
370 let mut precond_blocks = Vec::with_capacity(structure.camera_blocks.len());
371
372 for (_, start_col, size) in &structure.camera_blocks {
373 let mut block = DMatrix::<f64>::zeros(*size, *size);
375
376 for local_col in 0..*size {
377 let global_col = start_col + local_col;
378 let row_indices = symbolic.row_idx_of_col_raw(global_col);
379 let col_values = hessian.val_of_col(global_col);
380
381 for (idx, &global_row) in row_indices.iter().enumerate() {
382 if global_row >= *start_col && global_row < start_col + size {
383 let local_row = global_row - start_col;
384 block[(local_row, local_col)] = col_values[idx];
385 }
386 }
387 }
388
389 let inv_block = match block.clone().try_inverse() {
391 Some(inv) => inv,
392 None => {
393 let reg = 1e-6 * block.diagonal().iter().sum::<f64>().abs() / *size as f64;
395 let reg = reg.max(1e-8);
396 for i in 0..*size {
397 block[(i, i)] += reg;
398 }
399 block
400 .try_inverse()
401 .unwrap_or_else(|| DMatrix::identity(*size, *size))
402 }
403 };
404
405 precond_blocks.push(inv_block);
406 }
407
408 Ok(precond_blocks)
409 }
410
411 fn apply_block_preconditioner(
415 &self,
416 r: &Mat<f64>,
417 precond_blocks: &[DMatrix<f64>],
418 ) -> LinAlgResult<Mat<f64>> {
419 let structure = self
420 .block_structure
421 .as_ref()
422 .ok_or_else(|| LinAlgError::InvalidInput("Block structure not initialized".into()))?;
423
424 let cam_dof = structure.camera_dof;
425 let mut z = Mat::<f64>::zeros(cam_dof, 1);
426 let cam_start = structure.camera_col_range().0;
427
428 for (block_idx, (_, start_col, size)) in structure.camera_blocks.iter().enumerate() {
429 let local_start = start_col - cam_start;
430 let inv_block = &precond_blocks[block_idx];
431
432 let mut r_block = DVector::<f64>::zeros(*size);
434 for i in 0..*size {
435 r_block[i] = r[(local_start + i, 0)];
436 }
437
438 let z_block = inv_block * r_block;
440
441 for i in 0..*size {
443 z[(local_start + i, 0)] = z_block[i];
444 }
445 }
446
447 Ok(z)
448 }
449
450 fn compute_schur_jacobi_preconditioner(&self) -> LinAlgResult<Vec<DMatrix<f64>>> {
462 let structure = self
463 .block_structure
464 .as_ref()
465 .ok_or_else(|| LinAlgError::InvalidInput("Block structure not initialized".into()))?;
466
467 let hessian = self
468 .hessian
469 .as_ref()
470 .ok_or_else(|| LinAlgError::InvalidInput("Hessian not computed".into()))?;
471
472 let symbolic = hessian.symbolic();
473
474 let visibility = &self.camera_to_landmark_visibility;
476
477 let precond_blocks: Vec<DMatrix<f64>> = structure
481 .camera_blocks
482 .par_iter()
483 .enumerate()
484 .map(|(cam_idx, (_, cam_col_start, cam_size))| {
485 let mut s_ii = DMatrix::<f64>::zeros(*cam_size, *cam_size);
487
488 for local_col in 0..*cam_size {
489 let global_col = cam_col_start + local_col;
490 let row_indices = symbolic.row_idx_of_col_raw(global_col);
491 let col_values = hessian.val_of_col(global_col);
492
493 for (idx, &global_row) in row_indices.iter().enumerate() {
494 if global_row >= *cam_col_start && global_row < cam_col_start + cam_size {
495 let local_row = global_row - cam_col_start;
496 s_ii[(local_row, local_col)] = col_values[idx];
497 }
498 }
499 }
500
501 let visible_landmarks = if cam_idx < visibility.len() {
504 &visibility[cam_idx]
505 } else {
506 &[] as &[usize]
507 };
508
509 for &lm_block_idx in visible_landmarks {
510 if lm_block_idx >= structure.landmark_blocks.len() {
511 continue;
512 }
513 let (_, lm_col_start, _) = &structure.landmark_blocks[lm_block_idx];
514
515 let mut h_cp = DMatrix::<f64>::zeros(*cam_size, 3);
517
518 for col_offset in 0..3 {
519 let global_col = lm_col_start + col_offset;
520 let row_indices = symbolic.row_idx_of_col_raw(global_col);
521 let col_values = hessian.val_of_col(global_col);
522
523 for (idx, &global_row) in row_indices.iter().enumerate() {
524 if global_row >= *cam_col_start && global_row < cam_col_start + cam_size
525 {
526 let local_row = global_row - cam_col_start;
527 h_cp[(local_row, col_offset)] = col_values[idx];
528 }
529 }
530 }
531
532 let hpp_inv = &self.landmark_block_inverses[lm_block_idx];
534
535 let mut temp = DMatrix::<f64>::zeros(*cam_size, 3);
538 for i in 0..*cam_size {
539 for j in 0..3 {
540 let mut sum = 0.0;
541 for k in 0..3 {
542 sum += h_cp[(i, k)] * hpp_inv[(k, j)];
543 }
544 temp[(i, j)] = sum;
545 }
546 }
547
548 for i in 0..*cam_size {
550 for j in 0..*cam_size {
551 let mut sum = 0.0;
552 for k in 0..3 {
553 sum += temp[(i, k)] * h_cp[(j, k)];
554 }
555 s_ii[(i, j)] -= sum;
556 }
557 }
558 }
559
560 match s_ii.clone().try_inverse() {
562 Some(inv) => inv,
563 None => {
564 let trace = s_ii.trace();
566 let reg = (1e-6 * trace.abs() / *cam_size as f64).max(1e-8);
567 for i in 0..*cam_size {
568 s_ii[(i, i)] += reg;
569 }
570 s_ii.try_inverse()
571 .unwrap_or_else(|| DMatrix::identity(*cam_size, *cam_size))
572 }
573 }
574 })
575 .collect();
576
577 Ok(precond_blocks)
578 }
579
580 fn solve_pcg_block(
583 &self,
584 b: &Mat<f64>,
585 precond_blocks: &[DMatrix<f64>],
586 workspace_lm: &mut [f64],
587 workspace_cam: &mut [f64],
588 ) -> LinAlgResult<Mat<f64>> {
589 let cam_dof = b.nrows();
590 let mut x = Mat::<f64>::zeros(cam_dof, 1);
591
592 let mut r = b.clone();
594
595 let mut z = self.apply_block_preconditioner(&r, precond_blocks)?;
597
598 let mut p = z.clone();
599 let mut rz_old = 0.0;
600 for i in 0..cam_dof {
601 rz_old += r[(i, 0)] * z[(i, 0)];
602 }
603
604 let b_norm: f64 = (0..cam_dof)
606 .map(|i| b[(i, 0)] * b[(i, 0)])
607 .sum::<f64>()
608 .sqrt();
609 let tol = self.cg_tolerance * b_norm.max(1.0);
610
611 let mut ap = Mat::<f64>::zeros(cam_dof, 1);
613
614 for iter in 0..self.max_cg_iterations {
615 for i in 0..cam_dof {
618 ap[(i, 0)] = 0.0;
619 }
620 self.apply_schur_operator_fast(&p, &mut ap, workspace_lm, workspace_cam)?;
621
622 let mut p_ap = 0.0;
624 for i in 0..cam_dof {
625 p_ap += p[(i, 0)] * ap[(i, 0)];
626 }
627
628 if p_ap.abs() < 1e-20 {
629 tracing::debug!("PCG: p^T*A*p near zero at iteration {}", iter);
630 break;
631 }
632
633 let alpha = rz_old / p_ap;
634
635 for i in 0..cam_dof {
637 x[(i, 0)] += alpha * p[(i, 0)];
638 }
639
640 for i in 0..cam_dof {
642 r[(i, 0)] -= alpha * ap[(i, 0)];
643 }
644
645 let r_norm: f64 = (0..cam_dof)
647 .map(|i| r[(i, 0)] * r[(i, 0)])
648 .sum::<f64>()
649 .sqrt();
650
651 if r_norm < tol {
652 tracing::debug!(
653 "PCG converged in {} iterations (residual={:.2e})",
654 iter + 1,
655 r_norm
656 );
657 break;
658 }
659
660 z = self.apply_block_preconditioner(&r, precond_blocks)?;
662
663 let mut rz_new = 0.0;
665 for i in 0..cam_dof {
666 rz_new += r[(i, 0)] * z[(i, 0)];
667 }
668
669 if rz_old.abs() < 1e-30 {
670 break;
671 }
672
673 let beta = rz_new / rz_old;
674
675 for i in 0..cam_dof {
677 p[(i, 0)] = z[(i, 0)] + beta * p[(i, 0)];
678 }
679
680 rz_old = rz_new;
681 }
682
683 Ok(x)
684 }
685
686 fn invert_landmark_blocks(&mut self, hessian: &SparseColMat<usize, f64>) -> LinAlgResult<()> {
691 let structure = self
692 .block_structure
693 .as_ref()
694 .ok_or_else(|| LinAlgError::InvalidInput("Block structure not initialized".into()))?;
695
696 let symbolic = hessian.symbolic();
697
698 let blocks: Vec<(usize, Matrix3<f64>)> = structure
700 .landmark_blocks
701 .iter()
702 .enumerate()
703 .map(|(i, (_, start_col, _))| {
704 let mut block = Matrix3::<f64>::zeros();
705
706 for local_col in 0..3 {
707 let global_col = start_col + local_col;
708 let row_indices = symbolic.row_idx_of_col_raw(global_col);
709 let col_values = hessian.val_of_col(global_col);
710
711 for (idx, &row) in row_indices.iter().enumerate() {
712 if row >= *start_col && row < start_col + 3 {
713 let local_row = row - start_col;
714 block[(local_row, local_col)] = col_values[idx];
715 }
716 }
717 }
718
719 (i, block)
720 })
721 .collect();
722
723 const CONDITION_THRESHOLD: f64 = 1e10;
726 const MIN_EIGENVALUE_THRESHOLD: f64 = 1e-12;
727 const REGULARIZATION_SCALE: f64 = 1e-6;
728
729 let results: Vec<Result<Matrix3<f64>, (usize, String)>> = blocks
730 .par_iter()
731 .map(|(i, block)| {
732 let eigenvalues = block.symmetric_eigenvalues();
734 let min_ev = eigenvalues.min();
735 let max_ev = eigenvalues.max();
736
737 if min_ev < MIN_EIGENVALUE_THRESHOLD {
738 let reg = REGULARIZATION_SCALE + max_ev * REGULARIZATION_SCALE;
740 let regularized = block + Matrix3::identity() * reg;
741 regularized.try_inverse().ok_or_else(|| {
742 (
743 *i,
744 format!("singular even with regularization (min_ev={:.2e})", min_ev),
745 )
746 })
747 } else if max_ev / min_ev > CONDITION_THRESHOLD {
748 let extra_reg = max_ev * REGULARIZATION_SCALE;
750 let regularized = block + Matrix3::identity() * extra_reg;
751 regularized.try_inverse().ok_or_else(|| {
752 (
753 *i,
754 format!("ill-conditioned (cond={:.2e})", max_ev / min_ev),
755 )
756 })
757 } else {
758 block
760 .try_inverse()
761 .ok_or_else(|| (*i, "singular".to_string()))
762 }
763 })
764 .collect();
765
766 self.landmark_block_inverses.clear();
768 self.landmark_block_inverses.reserve(results.len());
769
770 for result in results {
771 match result {
772 Ok(inv) => self.landmark_block_inverses.push(inv),
773 Err((i, msg)) => {
774 return Err(LinAlgError::SingularMatrix(format!(
775 "Landmark block {} {}",
776 i, msg
777 )));
778 }
779 }
780 }
781
782 Ok(())
783 }
784
785 fn build_visibility_index(&mut self, hessian: &SparseColMat<usize, f64>) -> LinAlgResult<()> {
790 let structure = self
791 .block_structure
792 .as_ref()
793 .ok_or_else(|| LinAlgError::InvalidInput("Block structure not initialized".into()))?;
794
795 let symbolic = hessian.symbolic();
796 let (cam_start, cam_end) = structure.camera_col_range();
797 let num_cameras = structure.camera_blocks.len();
798
799 let mut cam_row_to_block: HashMap<usize, usize> = HashMap::new();
801 for (cam_idx, (_, start_col, size)) in structure.camera_blocks.iter().enumerate() {
802 for offset in 0..*size {
803 cam_row_to_block.insert(start_col + offset, cam_idx);
804 }
805 }
806
807 let mut visibility: Vec<Vec<usize>> = vec![Vec::new(); num_cameras];
809
810 for (lm_block_idx, (_, lm_col_start, _)) in structure.landmark_blocks.iter().enumerate() {
812 let global_col = *lm_col_start;
814 if global_col >= hessian.ncols() {
815 continue;
816 }
817
818 let row_indices = symbolic.row_idx_of_col_raw(global_col);
819
820 for &row in row_indices {
822 if row >= cam_start
823 && row < cam_end
824 && let Some(&cam_idx) = cam_row_to_block.get(&row)
825 {
826 if visibility[cam_idx].last() != Some(&lm_block_idx) {
828 visibility[cam_idx].push(lm_block_idx);
829 }
830 }
831 }
832 }
833
834 self.camera_to_landmark_visibility = visibility;
835 Ok(())
836 }
837
838 fn solve_with_cached_hessian(&mut self) -> LinAlgResult<Mat<f64>> {
841 let hessian = self
842 .hessian
843 .as_ref()
844 .ok_or_else(|| LinAlgError::InvalidInput("Hessian not cached".into()))?
845 .clone();
846 let gradient = self
847 .gradient
848 .as_ref()
849 .ok_or_else(|| LinAlgError::InvalidInput("Gradient not cached".into()))?
850 .clone();
851
852 let structure = self
854 .block_structure
855 .as_ref()
856 .ok_or_else(|| LinAlgError::InvalidInput("Block structure not initialized".into()))?;
857 let cam_dof = structure.camera_dof;
858 let lm_dof = structure.landmark_dof;
859 let (cam_start, _cam_end) = structure.camera_col_range();
860 let (lm_start, _lm_end) = structure.landmark_col_range();
861
862 self.invert_landmark_blocks(&hessian)?;
864
865 self.build_visibility_index(&hessian)?;
867
868 let mut g_reduced = Mat::<f64>::zeros(cam_dof, 1);
870 for i in 0..cam_dof {
871 g_reduced[(i, 0)] = gradient[(cam_start + i, 0)];
872 }
873
874 let mut g_lm = Mat::<f64>::zeros(lm_dof, 1);
875 for i in 0..lm_dof {
876 g_lm[(i, 0)] = gradient[(lm_start + i, 0)];
877 }
878
879 let mut temp = Mat::<f64>::zeros(lm_dof, 1);
880 self.apply_landmark_inverse(&g_lm, &mut temp)?;
881
882 let correction = self.extract_camera_landmark_mvp(&hessian, &temp)?;
883 for i in 0..cam_dof {
884 g_reduced[(i, 0)] -= correction[(i, 0)];
885 }
886
887 self.init_workspaces();
889
890 let precond_blocks = match self.preconditioner_type {
892 SchurPreconditioner::SchurJacobi => {
893 self.compute_schur_jacobi_preconditioner()?
895 }
896 SchurPreconditioner::BlockDiagonal => {
897 self.compute_block_preconditioner()?
899 }
900 SchurPreconditioner::None => {
901 let structure = self.block_structure.as_ref().ok_or_else(|| {
903 LinAlgError::InvalidInput("Block structure not initialized".into())
904 })?;
905 structure
906 .camera_blocks
907 .iter()
908 .map(|(_, _, size)| DMatrix::identity(*size, *size))
909 .collect()
910 }
911 };
912
913 let mut workspace_lm = std::mem::take(&mut self.workspace_lm);
915 let mut workspace_cam = std::mem::take(&mut self.workspace_cam);
916
917 let delta_cam = self.solve_pcg_block(
918 &g_reduced,
919 &precond_blocks,
920 &mut workspace_lm,
921 &mut workspace_cam,
922 )?;
923
924 self.workspace_lm = workspace_lm;
926 self.workspace_cam = workspace_cam;
927
928 let hcp_t_delta_cam = self.extract_camera_landmark_transpose_mvp(&hessian, &delta_cam)?;
930
931 let mut rhs_lm = Mat::<f64>::zeros(lm_dof, 1);
932 for i in 0..lm_dof {
933 rhs_lm[(i, 0)] = g_lm[(i, 0)] - hcp_t_delta_cam[(i, 0)];
934 }
935
936 let mut delta_lm = Mat::<f64>::zeros(lm_dof, 1);
937 self.apply_landmark_inverse(&rhs_lm, &mut delta_lm)?;
938
939 let total_dof = cam_dof + lm_dof;
941 let mut delta = Mat::<f64>::zeros(total_dof, 1);
942
943 for i in 0..cam_dof {
944 delta[(cam_start + i, 0)] = delta_cam[(i, 0)];
945 }
946 for i in 0..lm_dof {
947 delta[(lm_start + i, 0)] = delta_lm[(i, 0)];
948 }
949
950 Ok(delta)
951 }
952}
953
954impl Default for IterativeSchurSolver {
955 fn default() -> Self {
956 Self::new()
957 }
958}
959
960impl StructureAware for IterativeSchurSolver {
961 fn initialize_structure(
962 &mut self,
963 variables: &SlotMap<VarKey, Box<dyn ManifoldVariable>>,
964 variable_index_map: &SecondaryMap<VarKey, usize>,
965 schur_landmark_keys: &std::collections::HashSet<VarKey>,
966 ) -> LinAlgResult<()> {
967 let mut structure = SchurBlockStructure::new();
968
969 for (key, variable) in variables {
970 let start_col = *variable_index_map.get(key).ok_or_else(|| {
971 LinAlgError::InvalidInput(format!("VarKey {:?} not in index map", key))
972 })?;
973 let size = variable.dof();
974
975 if schur_landmark_keys.contains(&key) {
976 structure.landmark_blocks.push((key, start_col, size));
977 structure.landmark_dof += size;
978
979 if size != 3 {
980 return Err(LinAlgError::InvalidInput(format!(
981 "Landmark {:?} has DOF {}, expected 3",
982 key, size
983 )));
984 }
985 structure.num_landmarks += 1;
986 } else {
987 structure.camera_blocks.push((key, start_col, size));
988 structure.camera_dof += size;
989 }
990 }
991
992 structure.camera_blocks.sort_by_key(|(_, col, _)| *col);
993 structure.landmark_blocks.sort_by_key(|(_, col, _)| *col);
994
995 if structure.camera_blocks.is_empty() {
996 return Err(LinAlgError::InvalidInput(
997 "No camera variables found".into(),
998 ));
999 }
1000 if structure.landmark_blocks.is_empty() {
1001 return Err(LinAlgError::InvalidInput(
1002 "No landmark variables found".into(),
1003 ));
1004 }
1005
1006 self.block_structure = Some(structure);
1007 Ok(())
1008 }
1009}
1010
1011impl LinearSolver<SparseMode> for IterativeSchurSolver {
1012 fn solve_normal_equation(
1013 &mut self,
1014 residuals: &Mat<f64>,
1015 jacobian: &SparseColMat<usize, f64>,
1016 ) -> LinAlgResult<Mat<f64>> {
1017 let jt = jacobian
1019 .transpose()
1020 .to_col_major()
1021 .map_err(|e| LinAlgError::MatrixConversion(format!("Transpose failed: {:?}", e)))?;
1022 let hessian = jt.mul(jacobian);
1023 let jtr = jacobian.transpose().mul(residuals);
1024 let mut gradient = Mat::<f64>::zeros(jtr.nrows(), 1);
1025 for i in 0..jtr.nrows() {
1026 gradient[(i, 0)] = -jtr[(i, 0)];
1027 }
1028
1029 self.hessian = Some(hessian);
1030 self.gradient = Some(gradient);
1031
1032 self.solve_with_cached_hessian()
1034 }
1035
1036 fn solve_augmented_equation(
1037 &mut self,
1038 residuals: &Mat<f64>,
1039 jacobian: &SparseColMat<usize, f64>,
1040 lambda: f64,
1041 ) -> LinAlgResult<Mat<f64>> {
1042 let jt = jacobian
1044 .transpose()
1045 .to_col_major()
1046 .map_err(|e| LinAlgError::MatrixConversion(format!("Transpose failed: {:?}", e)))?;
1047 let jtr = jt.mul(residuals);
1048 let mut hessian = jacobian
1049 .transpose()
1050 .to_col_major()
1051 .map_err(|e| LinAlgError::MatrixConversion(format!("Transpose failed: {:?}", e)))?
1052 .mul(jacobian);
1053
1054 let n = hessian.ncols();
1056 let symbolic = hessian.symbolic();
1057 let mut triplets = Vec::new();
1058
1059 for col in 0..n {
1060 let row_indices = symbolic.row_idx_of_col_raw(col);
1061 let col_values = hessian.val_of_col(col);
1062
1063 for (idx, &row) in row_indices.iter().enumerate() {
1064 triplets.push(Triplet::new(row, col, col_values[idx]));
1065 }
1066
1067 triplets.push(Triplet::new(col, col, lambda));
1069 }
1070
1071 hessian = SparseColMat::try_new_from_triplets(n, n, &triplets).map_err(|e| {
1072 LinAlgError::InvalidInput(format!("Failed to build damped Hessian: {:?}", e))
1073 })?;
1074
1075 let mut gradient = Mat::<f64>::zeros(jtr.nrows(), 1);
1076 for i in 0..jtr.nrows() {
1077 gradient[(i, 0)] = -jtr[(i, 0)];
1078 }
1079
1080 self.hessian = Some(hessian);
1081 self.gradient = Some(gradient.clone());
1082
1083 self.solve_with_cached_hessian()
1086 }
1087
1088 fn get_hessian(&self) -> Option<&SparseColMat<usize, f64>> {
1089 self.hessian.as_ref()
1090 }
1091
1092 fn get_gradient(&self) -> Option<&Mat<f64>> {
1093 self.gradient.as_ref()
1094 }
1095}
1096
1097#[cfg(test)]
1098mod tests {
1099 use super::*;
1100 use crate::core::VarKey;
1101 use crate::core::variable::Variable;
1102 use apex_manifolds::{LieGroup, rn, se3};
1103 use slotmap::{SecondaryMap, SlotMap};
1104
1105 type TestResult = Result<(), Box<dyn std::error::Error>>;
1106
1107 type TestSetup = (
1108 SlotMap<VarKey, Box<dyn ManifoldVariable>>,
1109 SecondaryMap<VarKey, usize>,
1110 SparseColMat<usize, f64>,
1111 faer::Mat<f64>,
1112 std::collections::HashSet<VarKey>,
1113 );
1114
1115 fn create_schur_test_setup() -> Result<TestSetup, Box<dyn std::error::Error>> {
1118 let se3_id = DVector::from_vec(vec![1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]);
1119 let pt_zero = DVector::from_vec(vec![0.0, 0.0, 0.0]);
1120
1121 let mut variables: SlotMap<VarKey, Box<dyn ManifoldVariable>> = SlotMap::with_key();
1122 let cam0 = variables.insert(Box::new(Variable::new(se3::SE3::from_param_slice(
1123 se3_id.as_slice(),
1124 ))));
1125 let cam1 = variables.insert(Box::new(Variable::new(se3::SE3::from_param_slice(
1126 se3_id.as_slice(),
1127 ))));
1128 let pt0 = variables.insert(Box::new(Variable::new(rn::Rn::new(pt_zero.clone()))));
1129 let pt1 = variables.insert(Box::new(Variable::new(rn::Rn::new(pt_zero.clone()))));
1130 let pt2 = variables.insert(Box::new(Variable::new(rn::Rn::new(pt_zero.clone()))));
1131
1132 let mut variable_index_map: SecondaryMap<VarKey, usize> = SecondaryMap::new();
1133 variable_index_map.insert(cam0, 0);
1134 variable_index_map.insert(cam1, 6);
1135 variable_index_map.insert(pt0, 12);
1136 variable_index_map.insert(pt1, 15);
1137 variable_index_map.insert(pt2, 18);
1138
1139 let cam_cols = [0usize, 6];
1141 let lm_cols = [12usize, 15, 18];
1142 let mut triplets: Vec<Triplet<usize, usize, f64>> = Vec::new();
1143 for (ci, &cam_col) in cam_cols.iter().enumerate() {
1144 for (li, &lm_col) in lm_cols.iter().enumerate() {
1145 let row_base = (ci * 3 + li) * 6;
1146 for k in 0..6 {
1147 triplets.push(Triplet::new(row_base + k, cam_col + k, 1.0));
1148 triplets.push(Triplet::new(row_base + k, lm_col + (k % 3), 1.0));
1149 }
1150 }
1151 }
1152
1153 let jacobian = SparseColMat::try_new_from_triplets(36, 21, &triplets)?;
1154 let residuals = faer::Mat::from_fn(36, 1, |i, _| (i % 5) as f64 * 0.1);
1155
1156 let mut landmark_keys = std::collections::HashSet::new();
1157 landmark_keys.insert(pt0);
1158 landmark_keys.insert(pt1);
1159 landmark_keys.insert(pt2);
1160
1161 Ok((
1162 variables,
1163 variable_index_map,
1164 jacobian,
1165 residuals,
1166 landmark_keys,
1167 ))
1168 }
1169
1170 #[test]
1171 fn test_iterative_schur_creation() {
1172 let solver = IterativeSchurSolver::new();
1173 assert_eq!(solver.max_cg_iterations, 500);
1175 assert_eq!(solver.cg_tolerance, 1e-9);
1176 assert_eq!(solver.preconditioner_type, SchurPreconditioner::SchurJacobi);
1177 }
1178
1179 #[test]
1180 fn test_with_custom_params() {
1181 let solver = IterativeSchurSolver::with_cg_params(100, 1e-8);
1182 assert_eq!(solver.max_cg_iterations, 100);
1183 assert_eq!(solver.cg_tolerance, 1e-8);
1184 assert_eq!(solver.preconditioner_type, SchurPreconditioner::SchurJacobi);
1186 }
1187
1188 #[test]
1189 fn test_with_full_config() {
1190 let solver =
1191 IterativeSchurSolver::with_config(200, 1e-10, SchurPreconditioner::BlockDiagonal);
1192 assert_eq!(solver.max_cg_iterations, 200);
1193 assert_eq!(solver.cg_tolerance, 1e-10);
1194 assert_eq!(
1195 solver.preconditioner_type,
1196 SchurPreconditioner::BlockDiagonal
1197 );
1198 }
1199
1200 #[test]
1206 fn test_iterative_schur_default() {
1207 let solver = IterativeSchurSolver::default();
1208 assert_eq!(solver.max_cg_iterations, 500);
1209 assert_eq!(solver.cg_tolerance, 1e-9);
1210 assert!(solver.block_structure.is_none());
1211 assert!(solver.hessian.is_none());
1212 assert!(solver.landmark_block_inverses.is_empty());
1213 }
1214
1215 #[test]
1217 fn test_iterative_schur_initialize_structure() -> TestResult {
1218 let (variables, variable_index_map, _, _, landmark_keys) = create_schur_test_setup()?;
1219 let mut solver = IterativeSchurSolver::new();
1220 solver.initialize_structure(&variables, &variable_index_map, &landmark_keys)?;
1221
1222 let bs = solver
1223 .block_structure
1224 .as_ref()
1225 .ok_or("block_structure is None")?;
1226 assert_eq!(bs.camera_blocks.len(), 2);
1227 assert_eq!(bs.landmark_blocks.len(), 3);
1228 assert_eq!(bs.camera_dof, 12);
1229 assert_eq!(bs.landmark_dof, 9);
1230 Ok(())
1231 }
1232
1233 #[test]
1235 fn test_iterative_schur_solve_normal_equation() -> TestResult {
1236 let (variables, variable_index_map, jacobian, residuals, landmark_keys) =
1237 create_schur_test_setup()?;
1238 let mut solver = IterativeSchurSolver::with_cg_params(500, 1e-6);
1239 solver.initialize_structure(&variables, &variable_index_map, &landmark_keys)?;
1240
1241 let delta =
1242 LinearSolver::<SparseMode>::solve_normal_equation(&mut solver, &residuals, &jacobian)?;
1243 assert_eq!(delta.nrows(), 21);
1244 assert_eq!(delta.ncols(), 1);
1245 Ok(())
1246 }
1247
1248 #[test]
1250 fn test_iterative_schur_solve_augmented_equation() -> TestResult {
1251 let (variables, variable_index_map, jacobian, residuals, landmark_keys) =
1252 create_schur_test_setup()?;
1253 let mut solver = IterativeSchurSolver::with_cg_params(500, 1e-6);
1254 solver.initialize_structure(&variables, &variable_index_map, &landmark_keys)?;
1255
1256 let delta = LinearSolver::<SparseMode>::solve_augmented_equation(
1257 &mut solver,
1258 &residuals,
1259 &jacobian,
1260 0.1,
1261 )?;
1262 assert_eq!(delta.nrows(), 21);
1263 Ok(())
1264 }
1265
1266 #[test]
1268 fn test_iterative_schur_get_hessian_gradient() -> TestResult {
1269 let (variables, variable_index_map, jacobian, residuals, landmark_keys) =
1270 create_schur_test_setup()?;
1271 let mut solver = IterativeSchurSolver::with_cg_params(500, 1e-6);
1272 solver.initialize_structure(&variables, &variable_index_map, &landmark_keys)?;
1273
1274 assert!(LinearSolver::<SparseMode>::get_hessian(&solver).is_none());
1275 assert!(LinearSolver::<SparseMode>::get_gradient(&solver).is_none());
1276
1277 LinearSolver::<SparseMode>::solve_normal_equation(&mut solver, &residuals, &jacobian)?;
1278
1279 let h = LinearSolver::<SparseMode>::get_hessian(&solver);
1280 let g = LinearSolver::<SparseMode>::get_gradient(&solver);
1281 assert!(h.is_some());
1282 assert!(g.is_some());
1283 let h = h.ok_or("hessian is None")?;
1284 let g = g.ok_or("gradient is None")?;
1285 assert_eq!(h.nrows(), 21);
1286 assert_eq!(g.nrows(), 21);
1287 Ok(())
1288 }
1289
1290 #[test]
1292 fn test_iterative_schur_invert_landmark_blocks() -> TestResult {
1293 let (variables, variable_index_map, jacobian, residuals, landmark_keys) =
1294 create_schur_test_setup()?;
1295 let mut solver = IterativeSchurSolver::with_cg_params(500, 1e-6);
1296 solver.initialize_structure(&variables, &variable_index_map, &landmark_keys)?;
1297
1298 LinearSolver::<SparseMode>::solve_normal_equation(&mut solver, &residuals, &jacobian)?;
1299
1300 assert_eq!(solver.landmark_block_inverses.len(), 3);
1302 Ok(())
1303 }
1304
1305 #[test]
1307 fn test_iterative_schur_build_visibility_index() -> TestResult {
1308 let (variables, variable_index_map, jacobian, residuals, landmark_keys) =
1309 create_schur_test_setup()?;
1310 let mut solver = IterativeSchurSolver::with_cg_params(500, 1e-6);
1311 solver.initialize_structure(&variables, &variable_index_map, &landmark_keys)?;
1312
1313 LinearSolver::<SparseMode>::solve_normal_equation(&mut solver, &residuals, &jacobian)?;
1314
1315 assert_eq!(solver.camera_to_landmark_visibility.len(), 2);
1317 for cam_visibility in &solver.camera_to_landmark_visibility {
1318 assert!(!cam_visibility.is_empty());
1319 }
1320 Ok(())
1321 }
1322
1323 #[test]
1325 fn test_iterative_schur_workspace_init() -> TestResult {
1326 let (variables, variable_index_map, jacobian, residuals, landmark_keys) =
1327 create_schur_test_setup()?;
1328 let mut solver = IterativeSchurSolver::with_cg_params(500, 1e-6);
1329 solver.initialize_structure(&variables, &variable_index_map, &landmark_keys)?;
1330
1331 LinearSolver::<SparseMode>::solve_normal_equation(&mut solver, &residuals, &jacobian)?;
1332
1333 assert_eq!(solver.workspace_lm.len(), 9); assert_eq!(solver.workspace_cam.len(), 12); Ok(())
1336 }
1337
1338 #[test]
1340 fn test_iterative_schur_block_diagonal_preconditioner() -> TestResult {
1341 let (variables, variable_index_map, jacobian, residuals, landmark_keys) =
1342 create_schur_test_setup()?;
1343 let mut solver =
1344 IterativeSchurSolver::with_config(500, 1e-6, SchurPreconditioner::BlockDiagonal);
1345 solver.initialize_structure(&variables, &variable_index_map, &landmark_keys)?;
1346
1347 let delta =
1348 LinearSolver::<SparseMode>::solve_normal_equation(&mut solver, &residuals, &jacobian)?;
1349 assert_eq!(delta.nrows(), 21);
1350 Ok(())
1351 }
1352
1353 #[test]
1355 fn test_iterative_schur_schur_jacobi_preconditioner() -> TestResult {
1356 let (variables, variable_index_map, jacobian, residuals, landmark_keys) =
1357 create_schur_test_setup()?;
1358 let mut solver =
1359 IterativeSchurSolver::with_config(500, 1e-6, SchurPreconditioner::SchurJacobi);
1360 solver.initialize_structure(&variables, &variable_index_map, &landmark_keys)?;
1361
1362 let delta =
1363 LinearSolver::<SparseMode>::solve_normal_equation(&mut solver, &residuals, &jacobian)?;
1364 assert_eq!(delta.nrows(), 21);
1365 Ok(())
1366 }
1367
1368 #[test]
1370 fn test_iterative_schur_no_preconditioner() -> TestResult {
1371 let (variables, variable_index_map, jacobian, residuals, landmark_keys) =
1372 create_schur_test_setup()?;
1373 let mut solver = IterativeSchurSolver::with_config(500, 1e-6, SchurPreconditioner::None);
1374 solver.initialize_structure(&variables, &variable_index_map, &landmark_keys)?;
1375
1376 let delta =
1377 LinearSolver::<SparseMode>::solve_normal_equation(&mut solver, &residuals, &jacobian)?;
1378 assert_eq!(delta.nrows(), 21);
1379 Ok(())
1380 }
1381
1382 #[test]
1384 fn test_iterative_schur_augmented_lambda_effect() -> TestResult {
1385 let (variables, variable_index_map, jacobian, residuals, landmark_keys) =
1386 create_schur_test_setup()?;
1387
1388 let mut s1 = IterativeSchurSolver::with_cg_params(500, 1e-6);
1389 s1.initialize_structure(&variables, &variable_index_map, &landmark_keys)?;
1390 let d1 = LinearSolver::<SparseMode>::solve_augmented_equation(
1391 &mut s1, &residuals, &jacobian, 0.001,
1392 )?;
1393
1394 let mut s2 = IterativeSchurSolver::with_cg_params(500, 1e-6);
1395 s2.initialize_structure(&variables, &variable_index_map, &landmark_keys)?;
1396 let d2 = LinearSolver::<SparseMode>::solve_augmented_equation(
1397 &mut s2, &residuals, &jacobian, 100.0,
1398 )?;
1399
1400 let norm_diff: f64 = (0..21).map(|i| (d1[(i, 0)] - d2[(i, 0)]).powi(2)).sum();
1401 assert!(
1402 norm_diff > 1e-10,
1403 "Different λ should yield different updates"
1404 );
1405 Ok(())
1406 }
1407
1408 #[test]
1410 fn test_iterative_schur_solve_without_init_returns_error() -> TestResult {
1411 let triplets: Vec<Triplet<usize, usize, f64>> = vec![Triplet::new(0, 0, 1.0)];
1412 let jacobian =
1413 SparseColMat::try_new_from_triplets(1, 1, &triplets).map_err(|e| format!("{e:?}"))?;
1414 let residuals = faer::Mat::from_fn(1, 1, |_, _| 1.0);
1415 let mut solver = IterativeSchurSolver::new();
1416
1417 let result =
1418 LinearSolver::<SparseMode>::solve_normal_equation(&mut solver, &residuals, &jacobian);
1419 assert!(result.is_err());
1420 Ok(())
1421 }
1422
1423 #[test]
1429 fn test_apply_landmark_inverse() -> TestResult {
1430 let (variables, variable_index_map, jacobian, residuals, landmark_keys) =
1431 create_schur_test_setup()?;
1432 let mut solver = IterativeSchurSolver::with_cg_params(500, 1e-6);
1433 solver.initialize_structure(&variables, &variable_index_map, &landmark_keys)?;
1434 LinearSolver::<SparseMode>::solve_normal_equation(&mut solver, &residuals, &jacobian)?;
1436
1437 let input = faer::Mat::from_fn(9, 1, |i, _| (i + 1) as f64);
1439 let mut output = faer::Mat::zeros(9, 1);
1440 solver.apply_landmark_inverse(&input, &mut output)?;
1441
1442 let norm: f64 = (0..9).map(|i| output[(i, 0)].powi(2)).sum::<f64>().sqrt();
1444 assert!(
1445 norm > 0.0,
1446 "apply_landmark_inverse should produce non-zero output"
1447 );
1448 Ok(())
1449 }
1450
1451 #[test]
1453 fn test_extract_camera_landmark_transpose_mvp() -> TestResult {
1454 let (variables, variable_index_map, jacobian, residuals, landmark_keys) =
1455 create_schur_test_setup()?;
1456 let mut solver = IterativeSchurSolver::with_cg_params(500, 1e-6);
1457 solver.initialize_structure(&variables, &variable_index_map, &landmark_keys)?;
1458 LinearSolver::<SparseMode>::solve_normal_equation(&mut solver, &residuals, &jacobian)?;
1459
1460 let hessian = solver.hessian.as_ref().ok_or("hessian is None")?;
1461
1462 let x = faer::Mat::from_fn(12, 1, |i, _| (i as f64) * 0.1);
1464 let result = solver.extract_camera_landmark_transpose_mvp(hessian, &x)?;
1465
1466 assert_eq!(result.nrows(), 9);
1468 assert_eq!(result.ncols(), 1);
1469 Ok(())
1470 }
1471
1472 #[test]
1474 fn test_extract_camera_landmark_mvp() -> TestResult {
1475 let (variables, variable_index_map, jacobian, residuals, landmark_keys) =
1476 create_schur_test_setup()?;
1477 let mut solver = IterativeSchurSolver::with_cg_params(500, 1e-6);
1478 solver.initialize_structure(&variables, &variable_index_map, &landmark_keys)?;
1479 LinearSolver::<SparseMode>::solve_normal_equation(&mut solver, &residuals, &jacobian)?;
1480
1481 let hessian = solver.hessian.as_ref().ok_or("hessian is None")?;
1482
1483 let x = faer::Mat::from_fn(9, 1, |i, _| (i as f64) * 0.1);
1485 let result = solver.extract_camera_landmark_mvp(hessian, &x)?;
1486
1487 assert_eq!(result.nrows(), 12);
1489 assert_eq!(result.ncols(), 1);
1490 Ok(())
1491 }
1492
1493 #[test]
1495 fn test_apply_block_preconditioner() -> TestResult {
1496 let (variables, variable_index_map, jacobian, residuals, landmark_keys) =
1497 create_schur_test_setup()?;
1498 let mut solver =
1499 IterativeSchurSolver::with_config(500, 1e-6, SchurPreconditioner::BlockDiagonal);
1500 solver.initialize_structure(&variables, &variable_index_map, &landmark_keys)?;
1501 LinearSolver::<SparseMode>::solve_normal_equation(&mut solver, &residuals, &jacobian)?;
1502
1503 let precond_blocks = solver.compute_block_preconditioner()?;
1505 assert_eq!(precond_blocks.len(), 2);
1507
1508 let r = faer::Mat::from_fn(12, 1, |i, _| (i + 1) as f64);
1510 let z = solver.apply_block_preconditioner(&r, &precond_blocks)?;
1511 assert_eq!(z.nrows(), 12);
1512 assert_eq!(z.ncols(), 1);
1513
1514 let norm: f64 = (0..12).map(|i| z[(i, 0)].powi(2)).sum::<f64>().sqrt();
1516 assert!(norm > 0.0, "Preconditioned vector should be non-zero");
1517 Ok(())
1518 }
1519
1520 #[test]
1522 fn test_implicit_initialize_structure_no_cameras_returns_error() {
1523 use crate::core::variable::Variable;
1524 use apex_manifolds::rn;
1525 use nalgebra::DVector;
1526
1527 let mut variables: SlotMap<VarKey, Box<dyn ManifoldVariable>> = SlotMap::with_key();
1528 let k = variables.insert(Box::new(Variable::new(rn::Rn::new(DVector::zeros(3)))));
1529 let mut variable_index_map: SecondaryMap<VarKey, usize> = SecondaryMap::new();
1530 variable_index_map.insert(k, 0);
1531 let mut landmark_keys = std::collections::HashSet::new();
1532 landmark_keys.insert(k);
1533
1534 let mut solver = IterativeSchurSolver::new();
1535 let result = solver.initialize_structure(&variables, &variable_index_map, &landmark_keys);
1536 assert!(
1537 result.is_err(),
1538 "Expected Err when no camera variables present"
1539 );
1540 }
1541
1542 #[test]
1544 fn test_implicit_initialize_structure_no_landmarks_returns_error() {
1545 use crate::core::variable::Variable;
1546 use apex_manifolds::se3;
1547
1548 let mut variables: SlotMap<VarKey, Box<dyn ManifoldVariable>> = SlotMap::with_key();
1549 let _k = variables.insert(Box::new(Variable::new(se3::SE3::from_param_slice(&[
1550 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
1551 ]))));
1552 let mut variable_index_map: SecondaryMap<VarKey, usize> = SecondaryMap::new();
1553 variable_index_map.insert(_k, 0);
1554 let landmark_keys = std::collections::HashSet::<VarKey>::new(); let mut solver = IterativeSchurSolver::new();
1557 let result = solver.initialize_structure(&variables, &variable_index_map, &landmark_keys);
1558 assert!(
1559 result.is_err(),
1560 "Expected Err when no landmark variables present"
1561 );
1562 }
1563}