1pub mod cpu;
26pub mod gpu;
27
28use slotmap::{SecondaryMap, SlotMap};
29
30use faer::Mat;
31use faer::sparse::{SparseColMat, Triplet};
32use smallvec::SmallVec;
33use thiserror::Error;
34
35use crate::core::VarKey;
36use crate::core::problem::Problem;
37use crate::core::variable::ManifoldVariable;
38use crate::{
39 core::{corrector::Corrector, residual_block::ResidualBlock},
40 linearizer::cpu::{DenseMode, LinearizationMode, SparseMode},
41};
42
43pub use cpu::sparse::SymbolicStructure;
44
45#[derive(Debug, Clone, Error)]
63pub enum LinearizerError {
64 #[error("Symbolic structure error: {0}")]
66 SymbolicStructure(String),
67
68 #[error("Parallel computation error: {0}")]
70 ParallelComputation(String),
71
72 #[error("Variable error: {0}")]
74 Variable(String),
75
76 #[error("Factor linearization failed: {0}")]
78 FactorLinearization(String),
79
80 #[error("Invalid input: {0}")]
82 InvalidInput(String),
83}
84
85pub type LinearizerResult<T> = Result<T, LinearizerError>;
87
88pub(crate) struct BlockLinearization {
98 pub variable_local_idx_size_list: SmallVec<[(usize, usize); 8]>,
101 pub residual_row_start_idx: usize,
103 pub residual_dim: usize,
105}
106
107pub(crate) fn split_by_row_offsets_mut<'a>(
112 buf: &'a mut [f64],
113 sorted_offsets_lens: &[(usize, usize)],
114) -> Vec<&'a mut [f64]> {
115 let mut remaining = buf;
116 let mut result = Vec::with_capacity(sorted_offsets_lens.len());
117 let mut current = 0usize;
118 for &(start, len) in sorted_offsets_lens {
119 let gap = start - current;
120 let (_, rest) = remaining.split_at_mut(gap);
121 let (slice, rest2) = rest.split_at_mut(len);
122 result.push(slice);
123 remaining = rest2;
124 current = start + len;
125 }
126 result
127}
128
129pub(crate) fn compute_block_into(
138 residual_block: &ResidualBlock,
139 variables: &SlotMap<VarKey, Box<dyn ManifoldVariable>>,
140 residual_slice: &mut [f64],
141 jacobian_buf: &mut [f64],
142) -> LinearizerResult<BlockLinearization> {
143 let mut param_slices: SmallVec<[&[f64]; 8]> = SmallVec::new();
144 let mut variable_local_idx_size_list: SmallVec<[(usize, usize); 8]> = SmallVec::new();
145 let mut count_variable_local_idx: usize = 0;
146
147 for &var_key in &residual_block.variable_keys {
148 if let Some(variable) = variables.get(var_key) {
149 param_slices.push(variable.as_param_slice());
150 let var_size = variable.dof();
151 variable_local_idx_size_list.push((count_variable_local_idx, var_size));
152 count_variable_local_idx += var_size;
153 }
154 }
155
156 let (rows, cols) = residual_block.factor.jacobian_shape();
157 debug_assert_eq!(jacobian_buf.len(), rows * cols);
158 {
159 let jac_mut = faer::mat::MatMut::from_column_major_slice_mut(jacobian_buf, rows, cols);
160 residual_block
161 .factor
162 .linearize(¶m_slices, residual_slice, Some(jac_mut));
163 }
164
165 if let Some(loss_func) = &residual_block.loss_func {
168 let squared_norm: f64 = residual_slice.iter().map(|x| x * x).sum();
169 let corrector = Corrector::new(loss_func.as_ref(), squared_norm);
170 corrector.correct_jacobian_in_place(residual_slice, jacobian_buf, rows, cols);
172 corrector.correct_residual_in_place(residual_slice);
173 }
174
175 Ok(BlockLinearization {
176 variable_local_idx_size_list,
177 residual_row_start_idx: residual_block.residual_row_start_idx,
178 residual_dim: rows,
179 })
180}
181
182pub trait AssemblyBackend: LinearizationMode {
197 fn assemble(
199 problem: &Problem,
200 variables: &SlotMap<VarKey, Box<dyn ManifoldVariable>>,
201 variable_index_map: &SecondaryMap<VarKey, usize>,
202 symbolic_structure: Option<&SymbolicStructure>,
203 total_dof: usize,
204 ) -> LinearizerResult<(Mat<f64>, Self::Jacobian)>;
205
206 fn compute_column_norms(jacobian: &Self::Jacobian) -> Vec<f64>;
208
209 fn apply_column_scaling(jacobian: &Self::Jacobian, scaling: &[f64]) -> Self::Jacobian;
212
213 fn apply_inverse_scaling(step: &Mat<f64>, scaling: &[f64]) -> Mat<f64>;
215
216 fn hessian_vec_product(hessian: &Self::Hessian, vec: &Mat<f64>) -> Mat<f64>;
218}
219
220impl AssemblyBackend for SparseMode {
221 fn assemble(
222 problem: &Problem,
223 variables: &SlotMap<VarKey, Box<dyn ManifoldVariable>>,
224 variable_index_map: &SecondaryMap<VarKey, usize>,
225 symbolic_structure: Option<&SymbolicStructure>,
226 _total_dof: usize,
227 ) -> LinearizerResult<(Mat<f64>, SparseColMat<usize, f64>)> {
228 let sym = symbolic_structure.ok_or_else(|| {
229 LinearizerError::InvalidInput("SparseMode requires symbolic structure".to_string())
230 })?;
231 crate::linearizer::cpu::sparse::assemble_sparse(problem, variables, variable_index_map, sym)
232 }
233
234 fn compute_column_norms(jacobian: &SparseColMat<usize, f64>) -> Vec<f64> {
235 let ncols = jacobian.ncols();
236 let sparse_ref = jacobian.as_ref();
237 (0..ncols)
238 .map(|c| {
239 let col_norm_squared: f64 =
240 sparse_ref.val_of_col(c).iter().map(|&val| val * val).sum();
241 col_norm_squared.sqrt()
242 })
243 .collect()
244 }
245
246 fn apply_column_scaling(
247 jacobian: &SparseColMat<usize, f64>,
248 scaling: &[f64],
249 ) -> SparseColMat<usize, f64> {
250 let ncols = jacobian.ncols();
251 let triplets: Vec<Triplet<usize, usize, f64>> =
252 (0..ncols).map(|c| Triplet::new(c, c, scaling[c])).collect();
253 let scaling_mat = match SparseColMat::try_new_from_triplets(ncols, ncols, &triplets) {
254 Ok(mat) => mat,
255 Err(_) => return jacobian.clone(),
256 };
257 jacobian * &scaling_mat
258 }
259
260 fn apply_inverse_scaling(step: &Mat<f64>, scaling: &[f64]) -> Mat<f64> {
261 let mut result = step.clone();
262 for i in 0..step.nrows() {
263 result[(i, 0)] *= scaling[i];
264 }
265 result
266 }
267
268 fn hessian_vec_product(hessian: &SparseColMat<usize, f64>, vec: &Mat<f64>) -> Mat<f64> {
269 use std::ops::Mul;
270 hessian.as_ref().mul(vec)
271 }
272}
273
274impl AssemblyBackend for DenseMode {
275 fn assemble(
276 problem: &Problem,
277 variables: &SlotMap<VarKey, Box<dyn ManifoldVariable>>,
278 variable_index_map: &SecondaryMap<VarKey, usize>,
279 _symbolic_structure: Option<&SymbolicStructure>,
280 total_dof: usize,
281 ) -> LinearizerResult<(Mat<f64>, Mat<f64>)> {
282 crate::linearizer::cpu::dense::assemble_dense(
283 problem,
284 variables,
285 variable_index_map,
286 total_dof,
287 )
288 }
289
290 fn compute_column_norms(jacobian: &Mat<f64>) -> Vec<f64> {
291 let ncols = jacobian.ncols();
292 (0..ncols)
293 .map(|c| {
294 let mut norm_sq = 0.0;
295 for r in 0..jacobian.nrows() {
296 let v = jacobian[(r, c)];
297 norm_sq += v * v;
298 }
299 norm_sq.sqrt()
300 })
301 .collect()
302 }
303
304 fn apply_column_scaling(jacobian: &Mat<f64>, scaling: &[f64]) -> Mat<f64> {
305 let mut result = jacobian.clone();
306 for c in 0..jacobian.ncols() {
307 for r in 0..jacobian.nrows() {
308 result[(r, c)] *= scaling[c];
309 }
310 }
311 result
312 }
313
314 fn apply_inverse_scaling(step: &Mat<f64>, scaling: &[f64]) -> Mat<f64> {
315 let mut result = step.clone();
316 for i in 0..step.nrows() {
317 result[(i, 0)] *= scaling[i];
318 }
319 result
320 }
321
322 fn hessian_vec_product(hessian: &Mat<f64>, vec: &Mat<f64>) -> Mat<f64> {
323 hessian * vec
324 }
325}
326
327#[cfg(test)]
328mod tests {
329 use super::*;
330 use crate::{
331 core::{VarKey, problem::Problem},
332 factors,
333 linalg::JacobianMode,
334 };
335 use apex_manifolds::ManifoldType;
336 use faer::prelude::ReborrowMut;
337 use nalgebra::dvector;
338 use slotmap::{SecondaryMap, SlotMap};
339
340 type TestResult = Result<(), Box<dyn std::error::Error>>;
341
342 struct LinearFactor {
343 target: f64,
344 }
345
346 impl factors::Factor for LinearFactor {
347 fn linearize(
348 &self,
349 params: &[&[f64]],
350 residual: &mut [f64],
351 jacobian: Option<faer::mat::MatMut<'_, f64>>,
352 ) {
353 residual[0] = params[0][0] - self.target;
354 if let Some(mut jac) = jacobian {
355 *jac.rb_mut().get_mut(0, 0) = 1.0;
356 }
357 }
358 fn residual_dim(&self) -> usize {
359 1
360 }
361 fn jacobian_shape(&self) -> (usize, usize) {
362 (1, 1)
363 }
364 }
365
366 fn one_var_problem() -> (Problem, VarKey) {
367 let mut problem = Problem::new(JacobianMode::Sparse);
368 let k = problem.add_variable(ManifoldType::RN, dvector![5.0]);
369 problem.add_residual_block(&[k], Box::new(LinearFactor { target: 0.0 }), None);
370 (problem, k)
371 }
372
373 #[allow(clippy::type_complexity)]
374 fn make_index_map(
375 problem: &Problem,
376 ) -> (
377 SlotMap<VarKey, Box<dyn crate::core::variable::ManifoldVariable>>,
378 SecondaryMap<VarKey, usize>,
379 usize,
380 ) {
381 let variables = problem.variables.clone();
382 let mut index_map: SecondaryMap<VarKey, usize> = SecondaryMap::new();
383 let mut offset = 0;
384 for (k, v) in &variables {
385 index_map.insert(k, offset);
386 offset += v.dof();
387 }
388 (variables, index_map, offset)
389 }
390
391 #[test]
396 fn test_compute_block_into_residual_value() -> TestResult {
397 let (problem, _k) = one_var_problem();
398 let (variables, _, _) = make_index_map(&problem);
399 let block = problem
400 .residual_blocks()
401 .values()
402 .next()
403 .ok_or("no blocks")?;
404 let mut residual_slice = vec![0.0f64; 1];
405 let mut jac_buf = vec![0.0f64; 1];
406 compute_block_into(block, &variables, &mut residual_slice, &mut jac_buf)?;
407 assert!((residual_slice[0] - 5.0).abs() < 1e-12);
408 Ok(())
409 }
410
411 #[test]
412 fn test_compute_block_into_jacobian_shape() -> TestResult {
413 let (problem, _k) = one_var_problem();
414 let (variables, _, _) = make_index_map(&problem);
415 let block = problem
416 .residual_blocks()
417 .values()
418 .next()
419 .ok_or("no blocks")?;
420 let mut residual_slice = vec![0.0f64; 1];
421 let mut jac_buf = vec![0.0f64; 1];
422 let result = compute_block_into(block, &variables, &mut residual_slice, &mut jac_buf)?;
423 assert_eq!(result.residual_dim, 1);
424 assert_eq!(jac_buf.len(), 1); Ok(())
426 }
427
428 #[test]
429 fn test_compute_block_into_variable_local_idx() -> TestResult {
430 let (problem, _k) = one_var_problem();
431 let (variables, _, _) = make_index_map(&problem);
432 let block = problem
433 .residual_blocks()
434 .values()
435 .next()
436 .ok_or("no blocks")?;
437 let mut residual_slice = vec![0.0f64; 1];
438 let mut jac_buf = vec![0.0f64; 1];
439 let result = compute_block_into(block, &variables, &mut residual_slice, &mut jac_buf)?;
440 assert_eq!(result.variable_local_idx_size_list.len(), 1);
441 let (local_idx, size) = result.variable_local_idx_size_list[0];
442 assert_eq!(local_idx, 0);
443 assert_eq!(size, 1);
444 Ok(())
445 }
446
447 #[test]
452 fn test_split_by_row_offsets_mut_basic() {
453 let mut buf = vec![1.0f64, 2.0, 3.0, 4.0, 5.0];
454 let offsets = vec![(0, 2), (3, 2)];
455 let slices = split_by_row_offsets_mut(&mut buf, &offsets);
456 assert_eq!(slices.len(), 2);
457 assert_eq!(slices[0], &[1.0, 2.0]);
458 assert_eq!(slices[1], &[4.0, 5.0]);
459 }
460
461 #[test]
462 fn test_split_by_row_offsets_mut_write() {
463 let mut buf = vec![0.0f64; 4];
464 let offsets = vec![(0, 2), (2, 2)];
465 {
466 let mut slices = split_by_row_offsets_mut(&mut buf, &offsets);
467 slices[0][0] = 1.0;
468 slices[0][1] = 2.0;
469 slices[1][0] = 3.0;
470 slices[1][1] = 4.0;
471 }
472 assert_eq!(buf, vec![1.0, 2.0, 3.0, 4.0]);
473 }
474
475 #[test]
480 fn test_sparse_backend_assemble() -> TestResult {
481 let (problem, _k) = one_var_problem();
482 let (variables, index_map, total_dof) = make_index_map(&problem);
483 let sym = crate::linearizer::cpu::sparse::build_symbolic_structure(
484 &problem, &variables, &index_map, total_dof,
485 )?;
486 let (residual, _) =
487 SparseMode::assemble(&problem, &variables, &index_map, Some(&sym), total_dof)?;
488 assert!((residual[(0, 0)] - 5.0).abs() < 1e-12);
489 Ok(())
490 }
491
492 #[test]
493 fn test_sparse_backend_assemble_no_symbolic_returns_error() -> TestResult {
494 let (problem, _k) = one_var_problem();
495 let (variables, index_map, total_dof) = make_index_map(&problem);
496 let result = SparseMode::assemble(&problem, &variables, &index_map, None, total_dof);
497 assert!(result.is_err());
498 Ok(())
499 }
500
501 #[test]
502 fn test_sparse_backend_compute_column_norms() -> TestResult {
503 let (problem, _k) = one_var_problem();
504 let (variables, index_map, total_dof) = make_index_map(&problem);
505 let sym = crate::linearizer::cpu::sparse::build_symbolic_structure(
506 &problem, &variables, &index_map, total_dof,
507 )?;
508 let (_, jacobian) =
509 SparseMode::assemble(&problem, &variables, &index_map, Some(&sym), total_dof)?;
510 let norms = SparseMode::compute_column_norms(&jacobian);
511 assert_eq!(norms.len(), 1);
512 assert!((norms[0] - 1.0).abs() < 1e-12);
513 Ok(())
514 }
515
516 #[test]
517 fn test_sparse_backend_apply_column_scaling() -> TestResult {
518 let (problem, _k) = one_var_problem();
519 let (variables, index_map, total_dof) = make_index_map(&problem);
520 let sym = crate::linearizer::cpu::sparse::build_symbolic_structure(
521 &problem, &variables, &index_map, total_dof,
522 )?;
523 let (_, jacobian) =
524 SparseMode::assemble(&problem, &variables, &index_map, Some(&sym), total_dof)?;
525 let scaling = vec![0.5_f64];
526 let scaled = SparseMode::apply_column_scaling(&jacobian, &scaling);
527 let val = scaled.as_ref().val_of_col(0)[0];
528 assert!((val - 0.5).abs() < 1e-12);
529 Ok(())
530 }
531
532 #[test]
533 fn test_sparse_backend_apply_inverse_scaling() {
534 let step = Mat::from_fn(1, 1, |_, _| 1.0_f64);
535 let scaling = vec![2.0_f64];
536 let result = SparseMode::apply_inverse_scaling(&step, &scaling);
537 assert!((result[(0, 0)] - 2.0).abs() < 1e-12);
538 }
539
540 #[test]
541 fn test_sparse_backend_hessian_vec_product() -> TestResult {
542 let triplets = vec![faer::sparse::Triplet::new(0usize, 0usize, 4.0_f64)];
543 let h = SparseColMat::try_new_from_triplets(1, 1, &triplets)?;
544 let v = Mat::from_fn(1, 1, |_, _| 2.0_f64);
545 let result = SparseMode::hessian_vec_product(&h, &v);
546 assert!((result[(0, 0)] - 8.0).abs() < 1e-12);
547 Ok(())
548 }
549
550 #[test]
555 fn test_dense_backend_assemble() -> TestResult {
556 let mut problem = Problem::new(JacobianMode::Dense);
557 let k = problem.add_variable(ManifoldType::RN, dvector![5.0]);
558 problem.add_residual_block(&[k], Box::new(LinearFactor { target: 0.0 }), None);
559 let (variables, index_map, total_dof) = make_index_map(&problem);
560 let (residual, _) = DenseMode::assemble(&problem, &variables, &index_map, None, total_dof)?;
561 assert!((residual[(0, 0)] - 5.0).abs() < 1e-12);
562 Ok(())
563 }
564
565 #[test]
566 fn test_dense_backend_compute_column_norms() {
567 let jacobian = Mat::from_fn(1, 1, |_, _| 1.0_f64);
568 let norms = DenseMode::compute_column_norms(&jacobian);
569 assert_eq!(norms.len(), 1);
570 assert!((norms[0] - 1.0).abs() < 1e-12);
571 }
572
573 #[test]
574 fn test_dense_backend_apply_column_scaling() {
575 let jacobian = Mat::from_fn(1, 1, |_, _| 1.0_f64);
576 let scaling = vec![0.5_f64];
577 let scaled = DenseMode::apply_column_scaling(&jacobian, &scaling);
578 assert!((scaled[(0, 0)] - 0.5).abs() < 1e-12);
579 }
580
581 #[test]
582 fn test_dense_backend_apply_inverse_scaling() {
583 let step = Mat::from_fn(1, 1, |_, _| 1.0_f64);
584 let scaling = vec![2.0_f64];
585 let result = DenseMode::apply_inverse_scaling(&step, &scaling);
586 assert!((result[(0, 0)] - 2.0).abs() < 1e-12);
587 }
588
589 #[test]
590 fn test_dense_backend_hessian_vec_product() {
591 let h = Mat::from_fn(1, 1, |_, _| 4.0_f64);
592 let v = Mat::from_fn(1, 1, |_, _| 2.0_f64);
593 let result = DenseMode::hessian_vec_product(&h, &v);
594 assert!((result[(0, 0)] - 8.0).abs() < 1e-12);
595 }
596}