1use faer::{
4 Mat,
5 sparse::{Argsort, Pair, SparseColMat, SymbolicSparseColMat},
6};
7use rayon::prelude::*;
8use slotmap::{SecondaryMap, SlotMap};
9
10use crate::core::VarKey;
11use crate::error::ErrorLogging;
12use crate::linearizer::{
13 BlockLinearization, LinearizerError, LinearizerResult, compute_block_into,
14 split_by_row_offsets_mut,
15};
16
17use crate::core::problem::Problem;
18use crate::core::variable::ManifoldVariable;
19
20pub struct SymbolicStructure {
22 pub pattern: SymbolicSparseColMat<usize>,
23 pub order: Argsort<usize>,
24}
25
26pub fn build_symbolic_structure(
28 problem: &Problem,
29 variables: &SlotMap<VarKey, Box<dyn ManifoldVariable>>,
30 variable_index_map: &SecondaryMap<VarKey, usize>,
31 total_dof: usize,
32) -> LinearizerResult<SymbolicStructure> {
33 let mut indices = Vec::<Pair<usize, usize>>::new();
34
35 problem.residual_blocks().iter().for_each(|(_, block)| {
36 let mut var_local_sizes = Vec::<(usize, usize)>::new();
37 let mut local_offset = 0;
38
39 for &var_key in &block.variable_keys {
40 if let Some(variable) = variables.get(var_key) {
41 var_local_sizes.push((local_offset, variable.dof()));
42 local_offset += variable.dof();
43 }
44 }
45
46 for (i, &var_key) in block.variable_keys.iter().enumerate() {
47 if let Some(&global_col) = variable_index_map.get(var_key) {
48 if let Some((_, var_size)) = var_local_sizes.get(i) {
49 for row in 0..block.factor.residual_dim() {
50 for col in 0..*var_size {
51 indices.push(Pair::new(
52 block.residual_row_start_idx + row,
53 global_col + col,
54 ));
55 }
56 }
57 }
58 }
59 }
60 });
61
62 let (pattern, order) = SymbolicSparseColMat::try_new_from_indices(
63 problem.total_residual_dimension,
64 total_dof,
65 &indices,
66 )
67 .map_err(|e| {
68 LinearizerError::SymbolicStructure(
69 "Failed to build symbolic sparse matrix structure".to_string(),
70 )
71 .log_with_source(e)
72 })?;
73
74 Ok(SymbolicStructure { pattern, order })
75}
76
77pub fn assemble_sparse(
79 problem: &Problem,
80 variables: &SlotMap<VarKey, Box<dyn ManifoldVariable>>,
81 variable_index_map: &SecondaryMap<VarKey, usize>,
82 symbolic_structure: &SymbolicStructure,
83) -> LinearizerResult<(Mat<f64>, SparseColMat<usize, f64>)> {
84 let total_nnz = symbolic_structure.pattern.compute_nnz();
85
86 let mut blocks: Vec<&crate::core::residual_block::ResidualBlock> =
88 problem.residual_blocks().values().collect();
89 blocks.sort_by_key(|b| b.residual_row_start_idx);
90
91 let mut residual_buf = vec![0.0f64; problem.total_residual_dimension];
93 let offsets_lens: Vec<(usize, usize)> = blocks
94 .iter()
95 .map(|b| (b.residual_row_start_idx, b.factor.residual_dim()))
96 .collect();
97 let residual_slices = split_by_row_offsets_mut(&mut residual_buf, &offsets_lens);
98
99 let mut jacobian_buffers: Vec<Vec<f64>> = blocks
103 .iter()
104 .map(|b| {
105 let (r, c) = b.factor.jacobian_shape();
106 vec![0.0f64; r * c]
107 })
108 .collect();
109
110 let block_results: Vec<LinearizerResult<BlockLinearization>> = jacobian_buffers
113 .par_iter_mut()
114 .zip(residual_slices.into_par_iter())
115 .zip(blocks.par_iter())
116 .map(|((jac_buf, res_slice), block)| {
117 jac_buf.fill(0.0);
118 compute_block_into(block, variables, res_slice, jac_buf.as_mut_slice())
119 })
120 .collect();
121
122 let block_results = block_results
123 .into_iter()
124 .collect::<LinearizerResult<Vec<_>>>()?;
125
126 let mut jacobian_values = Vec::with_capacity(total_nnz);
128 for ((bl, block), jac_buf) in block_results
129 .iter()
130 .zip(blocks.iter())
131 .zip(jacobian_buffers.iter())
132 {
133 scatter_sparse_block(bl, block, variable_index_map, jac_buf, &mut jacobian_values)?;
134 }
135
136 let n = problem.total_residual_dimension;
138 let residual_faer = faer::Mat::from_fn(n, 1, |i, _| residual_buf[i]);
139
140 let jacobian_sparse = SparseColMat::new_from_argsort(
141 symbolic_structure.pattern.clone(),
142 &symbolic_structure.order,
143 jacobian_values.as_slice(),
144 )
145 .map_err(|e| {
146 LinearizerError::SymbolicStructure(
147 "Failed to create sparse Jacobian from argsort".to_string(),
148 )
149 .log_with_source(e)
150 })?;
151
152 Ok((residual_faer, jacobian_sparse))
153}
154
155fn scatter_sparse_block(
156 bl: &BlockLinearization,
157 residual_block: &crate::core::residual_block::ResidualBlock,
158 variable_index_map: &SecondaryMap<VarKey, usize>,
159 jacobian_buf: &[f64],
160 jacobian_values: &mut Vec<f64>,
161) -> LinearizerResult<()> {
162 for (i, &var_key) in residual_block.variable_keys.iter().enumerate() {
163 if variable_index_map.contains_key(var_key) {
164 let (local_col, var_size) = bl.variable_local_idx_size_list[i];
165 for row in 0..bl.residual_dim {
168 for col in 0..var_size {
169 jacobian_values.push(jacobian_buf[(local_col + col) * bl.residual_dim + row]);
170 }
171 }
172 } else {
173 return Err(LinearizerError::Variable(format!(
174 "VarKey {:?} missing in variable-to-column-index mapping",
175 var_key
176 ))
177 .log());
178 }
179 }
180 Ok(())
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use crate::{core::problem::Problem, factors, linalg::JacobianMode};
187 use apex_manifolds::ManifoldType;
188 use faer::prelude::ReborrowMut;
189 use nalgebra::dvector;
190
191 type TestResult = Result<(), Box<dyn std::error::Error>>;
192
193 struct LinearFactor {
194 target: f64,
195 }
196
197 impl factors::Factor for LinearFactor {
198 fn linearize(
199 &self,
200 params: &[&[f64]],
201 residual: &mut [f64],
202 jacobian: Option<faer::mat::MatMut<'_, f64>>,
203 ) {
204 residual[0] = params[0][0] - self.target;
205 if let Some(mut jac) = jacobian {
206 *jac.rb_mut().get_mut(0, 0) = 1.0;
207 }
208 }
209 fn residual_dim(&self) -> usize {
210 1
211 }
212 fn jacobian_shape(&self) -> (usize, usize) {
213 (1, 1)
214 }
215 }
216
217 fn one_var_problem() -> (Problem, VarKey) {
218 let mut problem = Problem::new(JacobianMode::Sparse);
219 let k = problem.add_variable(ManifoldType::RN, dvector![5.0]);
220 problem.add_residual_block(&[k], Box::new(LinearFactor { target: 0.0 }), None);
221 (problem, k)
222 }
223
224 fn build_index_map(problem: &Problem) -> (SecondaryMap<VarKey, usize>, usize) {
225 let mut map = SecondaryMap::new();
226 let mut offset = 0;
227 for (k, v) in &problem.variables {
228 map.insert(k, offset);
229 offset += v.dof();
230 }
231 (map, offset)
232 }
233
234 #[test]
235 fn test_build_symbolic_structure_nnz() -> TestResult {
236 let (problem, _) = one_var_problem();
237 let (index_map, total_dof) = build_index_map(&problem);
238 let sym = build_symbolic_structure(&problem, &problem.variables, &index_map, total_dof)?;
239 assert_eq!(sym.pattern.compute_nnz(), 1);
240 Ok(())
241 }
242
243 #[test]
244 fn test_build_symbolic_structure_dimensions() -> TestResult {
245 let (problem, _) = one_var_problem();
246 let (index_map, total_dof) = build_index_map(&problem);
247 let sym = build_symbolic_structure(&problem, &problem.variables, &index_map, total_dof)?;
248 assert_eq!(sym.pattern.nrows(), 1);
249 assert_eq!(sym.pattern.ncols(), 1);
250 Ok(())
251 }
252
253 #[test]
254 fn test_build_symbolic_structure_two_factors() -> TestResult {
255 let mut problem = Problem::new(JacobianMode::Sparse);
256 let k = problem.add_variable(ManifoldType::RN, dvector![5.0]);
257 problem.add_residual_block(&[k], Box::new(LinearFactor { target: 0.0 }), None);
258 problem.add_residual_block(&[k], Box::new(LinearFactor { target: 1.0 }), None);
259 let (index_map, total_dof) = build_index_map(&problem);
260 let sym = build_symbolic_structure(&problem, &problem.variables, &index_map, total_dof)?;
261 assert_eq!(sym.pattern.compute_nnz(), 2);
262 Ok(())
263 }
264
265 #[test]
266 fn test_assemble_sparse_basic() -> TestResult {
267 let (problem, _) = one_var_problem();
268 let (index_map, total_dof) = build_index_map(&problem);
269 let sym = build_symbolic_structure(&problem, &problem.variables, &index_map, total_dof)?;
270 let (residual, _) = assemble_sparse(&problem, &problem.variables, &index_map, &sym)?;
271 assert!((residual[(0, 0)] - 5.0).abs() < 1e-12);
272 Ok(())
273 }
274
275 #[test]
276 fn test_assemble_sparse_jacobian_value() -> TestResult {
277 let (problem, _) = one_var_problem();
278 let (index_map, total_dof) = build_index_map(&problem);
279 let sym = build_symbolic_structure(&problem, &problem.variables, &index_map, total_dof)?;
280 let (_, jacobian) = assemble_sparse(&problem, &problem.variables, &index_map, &sym)?;
281 let val = jacobian.as_ref().val_of_col(0)[0];
282 assert!((val - 1.0).abs() < 1e-12);
283 Ok(())
284 }
285
286 #[test]
287 fn test_assemble_sparse_zero_residual() -> TestResult {
288 let mut problem = Problem::new(JacobianMode::Sparse);
289 let k = problem.add_variable(ManifoldType::RN, dvector![3.0]);
290 problem.add_residual_block(&[k], Box::new(LinearFactor { target: 3.0 }), None);
291 let (index_map, total_dof) = build_index_map(&problem);
292 let sym = build_symbolic_structure(&problem, &problem.variables, &index_map, total_dof)?;
293 let (residual, _) = assemble_sparse(&problem, &problem.variables, &index_map, &sym)?;
294 assert!(residual[(0, 0)].abs() < 1e-12);
295 Ok(())
296 }
297
298 #[test]
299 fn test_assemble_sparse_dimensions() -> TestResult {
300 let (problem, _) = one_var_problem();
301 let (index_map, total_dof) = build_index_map(&problem);
302 let sym = build_symbolic_structure(&problem, &problem.variables, &index_map, total_dof)?;
303 let (residual, jacobian) = assemble_sparse(&problem, &problem.variables, &index_map, &sym)?;
304 assert_eq!(residual.nrows(), 1);
305 assert_eq!(residual.ncols(), 1);
306 assert_eq!(jacobian.nrows(), 1);
307 assert_eq!(jacobian.ncols(), 1);
308 Ok(())
309 }
310
311 #[test]
312 fn test_assemble_sparse_two_variables() -> TestResult {
313 let mut problem = Problem::new(JacobianMode::Sparse);
314 let kx = problem.add_variable(ManifoldType::RN, dvector![2.0]);
315 let ky = problem.add_variable(ManifoldType::RN, dvector![7.0]);
316 problem.add_residual_block(&[kx], Box::new(LinearFactor { target: 0.0 }), None);
317 problem.add_residual_block(&[ky], Box::new(LinearFactor { target: 0.0 }), None);
318 let (index_map, total_dof) = build_index_map(&problem);
319 let sym = build_symbolic_structure(&problem, &problem.variables, &index_map, total_dof)?;
320 let (residual, _) = assemble_sparse(&problem, &problem.variables, &index_map, &sym)?;
321 assert_eq!(residual.nrows(), 2);
322 let rsum = residual[(0, 0)].abs() + residual[(1, 0)].abs();
323 assert!((rsum - 9.0).abs() < 1e-12);
324 Ok(())
325 }
326
327 #[test]
328 fn test_assemble_sparse_missing_variable_key_returns_error() -> TestResult {
329 let (problem, _) = one_var_problem();
330 let (_, total_dof) = build_index_map(&problem);
331 let (index_map_full, _) = build_index_map(&problem);
332 let sym =
333 build_symbolic_structure(&problem, &problem.variables, &index_map_full, total_dof)?;
334
335 let empty: SecondaryMap<VarKey, usize> = SecondaryMap::new();
336 let result = assemble_sparse(&problem, &problem.variables, &empty, &sym);
337 assert!(result.is_err(), "expected Err for missing variable key");
338 Ok(())
339 }
340}