Skip to content

Commit 1785aa9

Browse files
jumpinthefireHossam Rabbouh
andauthored
Precompute symbolic matrix to hold jacobian (#41)
* Pre-compute symbolic matrix for jacobians * Remove reserve * Formatting * Try to fix pipeline * Try to fix pipeline * Post-review change * Formatting --------- Co-authored-by: Hossam Rabbouh <hrabbouh@Hossams-MacBook-Pro-2.local>
1 parent 64a9f59 commit 1785aa9

6 files changed

Lines changed: 122 additions & 41 deletions

File tree

.github/workflows/rust.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ jobs:
1616

1717
steps:
1818
- uses: actions/checkout@v4
19+
- name: Dependencies
20+
run: sudo apt install pkg-config libfreetype6-dev libfontconfig1-dev
1921
- name: Format
2022
run: cargo fmt --check --verbose
2123
- name: Linting

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
[package]
22
name = "tiny-solver"
3-
version = "0.14.2"
3+
version = "0.15.0"
44
edition = "2021"
5-
authors = ["Powei Lin <poweilin1994@gmail.com>"]
5+
authors = ["Powei Lin <poweilin1994@gmail.com>, Hossam R. <hrabbouh@gmail.com>"]
66
readme = "README.md"
77
license = "MIT OR Apache-2.0"
88
description = "Factor graph solver"

src/optimizer/gauss_newton_optimizer.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,29 @@ impl optimizer::Optimizer for GaussNewtonOptimizer {
4343
LinearSolverType::SparseQR => Box::new(linear::SparseQRSolver::new()),
4444
};
4545

46+
let symbolic_structure = problem.build_symbolic_structure(
47+
&parameter_blocks,
48+
total_variable_dimension,
49+
&variable_name_to_col_idx_dict,
50+
);
51+
4652
let mut last_err: f64 = 1.0;
4753

4854
for i in 0..opt_option.max_iteration {
55+
let start = Instant::now();
4956
let (residuals, jac) = problem.compute_residual_and_jacobian(
5057
&parameter_blocks,
5158
&variable_name_to_col_idx_dict,
5259
total_variable_dimension,
60+
&symbolic_structure,
5361
);
5462
let current_error = residuals.norm_l2();
55-
trace!("iter:{} total err:{}", i, current_error);
63+
trace!(
64+
"iter:{}, total err:{}, duration: {:?}",
65+
i,
66+
current_error,
67+
start.elapsed()
68+
);
5669

5770
if current_error < opt_option.min_error_threshold {
5871
trace!("error too low");

src/optimizer/levenberg_marquardt_optimizer.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ impl optimizer::Optimizer for LevenbergMarquardtOptimizer {
6767
// With LM, rather than solving A * dx = b for dx, we solve for (A + lambda * diag(A)) dx = b.
6868
let mut jacobi_scaling_diagonal: Option<faer::sparse::SparseColMat<usize, f64>> = None;
6969

70+
let s = problem.build_symbolic_structure(
71+
&parameter_blocks,
72+
total_variable_dimension,
73+
&variable_name_to_col_idx_dict,
74+
);
75+
7076
// Damping parameter (a.k.a lambda / Marquardt parameter)
7177
let mut u = 1.0 / self.initial_trust_region_radius;
7278

@@ -76,6 +82,7 @@ impl optimizer::Optimizer for LevenbergMarquardtOptimizer {
7682
&parameter_blocks,
7783
&variable_name_to_col_idx_dict,
7884
total_variable_dimension,
85+
&s,
7986
);
8087

8188
if i == 0 {
@@ -186,7 +193,7 @@ impl optimizer::Optimizer for LevenbergMarquardtOptimizer {
186193
} else {
187194
// If there's too much divergence, reduce the trust region and try again with the same parameters.
188195
u *= 2.0;
189-
println!("u {}", u);
196+
trace!("u {}", u);
190197
}
191198
} else {
192199
log::debug!("solve ax=b failed");

src/problem.rs

Lines changed: 90 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::collections::{HashMap, HashSet};
22
use std::sync::{Arc, Mutex};
33

4-
use faer::sparse::SparseColMat;
4+
use faer::sparse::{SparseColMat, SymbolicSparseColMat, ValuesOrder};
55
use faer_ext::IntoFaer;
66
use nalgebra as na;
77
use rayon::prelude::*;
@@ -24,8 +24,12 @@ impl Default for Problem {
2424
}
2525
}
2626

27-
/// (col idx in matrix, row idx in matrix, value)
28-
type JacobianValue = (usize, usize, f64);
27+
pub struct SymbolicStructure {
28+
pattern: SymbolicSparseColMat<usize>,
29+
order: ValuesOrder<usize>,
30+
}
31+
32+
type JacobianValue = f64;
2933

3034
impl Problem {
3135
pub fn new() -> Problem {
@@ -39,6 +43,51 @@ impl Problem {
3943
}
4044
}
4145

46+
pub fn build_symbolic_structure(
47+
&self,
48+
parameter_blocks: &HashMap<String, ParameterBlock>,
49+
total_variable_dimension: usize,
50+
variable_name_to_col_idx_dict: &HashMap<String, usize>,
51+
) -> SymbolicStructure {
52+
let mut indices = Vec::<(usize, usize)>::new();
53+
54+
self.residual_blocks.iter().for_each(|(_, residual_block)| {
55+
let mut variable_local_idx_size_list = Vec::<(usize, usize)>::new();
56+
let mut count_variable_local_idx: usize = 0;
57+
for var_key in &residual_block.variable_key_list {
58+
if let Some(param) = parameter_blocks.get(var_key) {
59+
variable_local_idx_size_list
60+
.push((count_variable_local_idx, param.tangent_size()));
61+
count_variable_local_idx += param.tangent_size();
62+
};
63+
}
64+
for (i, var_key) in residual_block.variable_key_list.iter().enumerate() {
65+
if let Some(variable_global_idx) = variable_name_to_col_idx_dict.get(var_key) {
66+
let (_, var_size) = variable_local_idx_size_list[i];
67+
for row_idx in 0..residual_block.dim_residual {
68+
for col_idx in 0..var_size {
69+
let global_row_idx = residual_block.residual_row_start_idx + row_idx;
70+
let global_col_idx = variable_global_idx + col_idx;
71+
indices.push((global_row_idx, global_col_idx));
72+
}
73+
}
74+
}
75+
}
76+
});
77+
let start = std::time::Instant::now();
78+
let (s, o) = SymbolicSparseColMat::try_new_from_indices(
79+
self.total_residual_dimension,
80+
total_variable_dimension,
81+
&indices,
82+
)
83+
.unwrap();
84+
log::trace!("Built symbolic matrix: {:?}", start.elapsed());
85+
SymbolicStructure {
86+
pattern: s,
87+
order: o,
88+
}
89+
}
90+
4291
pub fn get_variable_name_to_col_idx_dict(
4392
&self,
4493
parameter_blocks: &HashMap<String, ParameterBlock>,
@@ -147,16 +196,14 @@ impl Problem {
147196
let total_residual = Arc::new(Mutex::new(na::DVector::<f64>::zeros(
148197
self.total_residual_dimension,
149198
)));
150-
self.residual_blocks
151-
.par_iter()
152-
.for_each(|(_, residual_block)| {
153-
self.compute_residual_impl(
154-
residual_block,
155-
parameter_blocks,
156-
&total_residual,
157-
with_loss_fn,
158-
)
159-
});
199+
self.residual_blocks.iter().for_each(|(_, residual_block)| {
200+
self.compute_residual_impl(
201+
residual_block,
202+
parameter_blocks,
203+
&total_residual,
204+
with_loss_fn,
205+
)
206+
});
160207
let total_residual = Arc::try_unwrap(total_residual)
161208
.unwrap()
162209
.into_inner()
@@ -170,42 +217,46 @@ impl Problem {
170217
parameter_blocks: &HashMap<String, ParameterBlock>,
171218
variable_name_to_col_idx_dict: &HashMap<String, usize>,
172219
total_variable_dimension: usize,
220+
symbolic_structure: &SymbolicStructure,
173221
) -> (faer::Mat<f64>, SparseColMat<usize, f64>) {
174222
// multi
175223
let total_residual = Arc::new(Mutex::new(na::DVector::<f64>::zeros(
176224
self.total_residual_dimension,
177225
)));
178-
let jacobian_list = Arc::new(Mutex::new(Vec::<JacobianValue>::new()));
179226

180-
self.residual_blocks
227+
let mut start = std::time::Instant::now();
228+
let jacobian_lists: Vec<JacobianValue> = self
229+
.residual_blocks
181230
.par_iter()
182-
.for_each(|(_, residual_block)| {
231+
.map(|(_, residual_block)| {
183232
self.compute_residual_and_jacobian_impl(
184233
residual_block,
185234
parameter_blocks,
186235
variable_name_to_col_idx_dict,
187236
&total_residual,
188-
&jacobian_list,
189237
)
190-
});
238+
})
239+
.flatten()
240+
.collect();
191241

242+
log::debug!("compute_residual_and_jacobian_impl: {:?}, total_variable_dim: {:?}, total_residual: {:?}", start.elapsed(), total_variable_dimension, self.total_residual_dimension);
243+
start = std::time::Instant::now();
192244
let total_residual = Arc::try_unwrap(total_residual)
193245
.unwrap()
194246
.into_inner()
195247
.unwrap();
196-
let jacobian_list = Arc::try_unwrap(jacobian_list)
197-
.unwrap()
198-
.into_inner()
199-
.unwrap();
200-
// end
201248

202249
let residual_faer = total_residual.view_range(.., ..).into_faer().to_owned();
203-
let jacobian_faer = SparseColMat::try_new_from_triplets(
204-
self.total_residual_dimension,
205-
total_variable_dimension,
206-
&jacobian_list,
250+
log::debug!("residual_faer: {:?}", start.elapsed());
251+
start = std::time::Instant::now();
252+
let jacobian_faer = SparseColMat::new_from_order_and_values(
253+
symbolic_structure.pattern.clone(),
254+
&symbolic_structure.order,
255+
jacobian_lists.as_slice(),
207256
)
208257
.unwrap();
258+
log::debug!("jacobian_faer: {:?}", start.elapsed());
259+
//log::debug!("rest of the function: {:?}", start.elapsed());
209260
(residual_faer, jacobian_faer)
210261
}
211262

@@ -241,8 +292,7 @@ impl Problem {
241292
parameter_blocks: &HashMap<String, ParameterBlock>,
242293
variable_name_to_col_idx_dict: &HashMap<String, usize>,
243294
total_residual: &Arc<Mutex<na::DVector<f64>>>,
244-
jacobian_list: &Arc<Mutex<Vec<JacobianValue>>>,
245-
) {
295+
) -> Vec<JacobianValue> {
246296
let mut params = Vec::new();
247297
let mut variable_local_idx_size_list = Vec::<(usize, usize)>::new();
248298
let mut count_variable_local_idx: usize = 0;
@@ -264,22 +314,25 @@ impl Problem {
264314
.copy_from(&res);
265315
}
266316

317+
let mut local_jacobian_list = Vec::new();
318+
267319
for (i, var_key) in residual_block.variable_key_list.iter().enumerate() {
268-
if let Some(variable_global_idx) = variable_name_to_col_idx_dict.get(var_key) {
320+
if variable_name_to_col_idx_dict.contains_key(var_key) {
269321
let (variable_local_idx, var_size) = variable_local_idx_size_list[i];
270322
let variable_jac = jac.view((0, variable_local_idx), (jac.shape().0, var_size));
271-
let mut local_jacobian_list = Vec::new();
272323
for row_idx in 0..jac.shape().0 {
273324
for col_idx in 0..var_size {
274-
let global_row_idx = residual_block.residual_row_start_idx + row_idx;
275-
let global_col_idx = variable_global_idx + col_idx;
276-
let value = variable_jac[(row_idx, col_idx)];
277-
local_jacobian_list.push((global_row_idx, global_col_idx, value));
325+
local_jacobian_list.push(variable_jac[(row_idx, col_idx)]);
278326
}
279327
}
280-
let mut jacobian_list = jacobian_list.lock().unwrap();
281-
jacobian_list.extend(local_jacobian_list);
328+
} else {
329+
panic!(
330+
"Missing key {} in variable-to-column-index mapping",
331+
var_key
332+
);
282333
}
283334
}
335+
336+
local_jacobian_list
284337
}
285338
}

tests/test_problem.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,17 @@ mod tests {
120120
let variable_name_to_col_idx_dict =
121121
problem.get_variable_name_to_col_idx_dict(&parameter_blocks);
122122
let total_variable_dimension = parameter_blocks.values().map(|p| p.tangent_size()).sum();
123+
let symbolic_structure = problem.build_symbolic_structure(
124+
&parameter_blocks,
125+
total_variable_dimension,
126+
&variable_name_to_col_idx_dict,
127+
);
123128

124129
let (residuals, jac) = problem.compute_residual_and_jacobian(
125130
&parameter_blocks,
126131
&variable_name_to_col_idx_dict,
127132
total_variable_dimension,
133+
&symbolic_structure,
128134
);
129135

130136
assert_eq!(residuals.nrows(), 3);

0 commit comments

Comments
 (0)