Skip to content

Commit 7141a7f

Browse files
authored
Add manifold (#36)
* huge refactor * save * save * manifold * read g2o * add parking * add docs * rename
1 parent f8590ae commit 7141a7f

23 files changed

Lines changed: 16345 additions & 176 deletions

Cargo.toml

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "tiny-solver"
3-
version = "0.13.0"
3+
version = "0.14.0"
44
edition = "2021"
55
authors = ["Powei Lin <poweilin1994@gmail.com>"]
66
readme = "README.md"
@@ -15,7 +15,7 @@ exclude = ["/.github/*", "*.ipynb", "./scripts/*", "examples/*", "tests/"]
1515
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1616

1717
[dependencies]
18-
faer = "0.20.1"
18+
faer = "0.20.2"
1919
faer-ext = { version = "0.4.1", features = ["nalgebra"] }
2020
log = "0.4.21"
2121
nalgebra = "0.33.2"
@@ -25,22 +25,28 @@ numpy = { version = "0.23.0", features = ["nalgebra"], optional = true }
2525
pyo3 = { version = "0.23.3", features = ["abi3", "abi3-py38"], optional = true }
2626
# pyo3-log = { version = "0.9.0", optional = true }
2727
rayon = "1.9.0"
28+
simba = "0.9.0"
2829

2930
[[example]]
3031
name = "m3500_benchmark"
3132
path = "examples/m3500_benchmark.rs"
3233

34+
[[example]]
35+
name = "sphere2500"
36+
37+
[[example]]
38+
name = "parking-garage"
39+
3340
[features]
3441
python = ["num-dual/python", "numpy", "pyo3"]
3542

3643
[dev-dependencies]
3744
env_logger = "0.11.6"
38-
itertools = "0.13.0"
45+
itertools = "0.14.0"
3946
plotters = "0.3.6"
4047

4148
[profile.dev.package.faer]
4249
opt-level = 3
4350

4451
[lib]
4552
name = "tiny_solver"
46-
# crate-type = ["staticlib"]

README.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ cargo add tiny-solver
2323
- [x] LevenbergMarquardtOptimizer
2424
- [x] Multithreading jacobian
2525
- [x] loss functions (Huber, CauchyLoss, ArctanLoss)
26-
- [x] Define factor in python
26+
- [x] Parameter on manifold (SO3, SE3)
2727

2828
#### TODO
2929
- [ ] information matrix
@@ -70,14 +70,14 @@ fn main() {
7070
// add residual x needs to be close to 3.0
7171
problem.add_residual_block(
7272
1,
73-
&[("x", 1)],
73+
&["x"],
7474
Box::new(tiny_solver::factors::PriorFactor {
7575
v: na::dvector![3.0],
7676
}),
7777
None,
7878
);
7979
// add custom residual for x and yz
80-
problem.add_residual_block(2, &[("x", 1), ("yz", 2)], Box::new(CustomFactor {}), None);
80+
problem.add_residual_block(2, &["x", "yz"], Box::new(CustomFactor), None);
8181

8282
// the initial values for x is 0.7 and yz is [-30.2, 123.4]
8383
let initial_values = HashMap::<String, na::DVector<f64>>::from([
@@ -167,4 +167,15 @@ cargo run -r --example m3500_benchmark
167167
# run python version
168168
pip install tiny-solver matplotlib
169169
python3 examples/python/m3500.py
170-
```
170+
```
171+
### Sphere 2500 dataset
172+
```
173+
cargo run -r --example sphere2500
174+
```
175+
<img src="docs/sphere2500_rs.png" width="300" alt="sp2500 dataset rust result.">
176+
177+
### Parking garage dataset
178+
```
179+
cargo run -r --example parking-garage
180+
```
181+
<img src="docs/parking_garage.png" width="300" alt="parking dataset rust result.">

docs/parking_garage.png

96.6 KB
Loading

docs/sphere2500_rs.png

198 KB
Loading

examples/m3500_benchmark.rs

Lines changed: 1 addition & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,8 @@
1-
use std::collections::HashMap;
2-
use std::fs::read_to_string;
31
use std::time::Instant;
42

5-
use nalgebra as na;
63
use plotters::prelude::*;
74

8-
use tiny_solver::{
9-
factors, loss_functions::HuberLoss, optimizer::Optimizer, problem, GaussNewtonOptimizer,
10-
};
11-
12-
fn read_g2o(filename: &str) -> (problem::Problem, HashMap<String, na::DVector<f64>>) {
13-
let mut problem = problem::Problem::new();
14-
let mut init_values = HashMap::<String, na::DVector<f64>>::new();
15-
for line in read_to_string(filename).unwrap().lines() {
16-
let line: Vec<&str> = line.split(' ').collect();
17-
match line[0] {
18-
"VERTEX_SE2" => {
19-
let x = line[2].parse::<f64>().unwrap();
20-
let y = line[3].parse::<f64>().unwrap();
21-
let theta = line[4].parse::<f64>().unwrap();
22-
init_values.insert(format!("x{}", line[1]), na::dvector![theta, x, y]);
23-
}
24-
"EDGE_SE2" => {
25-
let id0 = format!("x{}", line[1]);
26-
let id1 = format!("x{}", line[2]);
27-
let dx = line[3].parse::<f64>().unwrap();
28-
let dy = line[4].parse::<f64>().unwrap();
29-
let dtheta = line[5].parse::<f64>().unwrap();
30-
// todo add info matrix
31-
let edge = factors::BetweenFactorSE2 { dx, dy, dtheta };
32-
problem.add_residual_block(
33-
3,
34-
&[(&id0, 3), (&id1, 3)],
35-
Box::new(edge),
36-
Some(Box::new(HuberLoss::new(1.0))),
37-
);
38-
}
39-
_ => {
40-
println!("err");
41-
break;
42-
}
43-
}
44-
}
45-
let origin_factor = factors::PriorFactor {
46-
v: na::dvector![0.0, 0.0, 0.0],
47-
};
48-
problem.add_residual_block(
49-
3,
50-
&[("x0", 3)],
51-
Box::new(origin_factor),
52-
Some(Box::new(HuberLoss::new(1.0))),
53-
);
54-
(problem, init_values)
55-
}
5+
use tiny_solver::{helper::read_g2o, optimizer::Optimizer, GaussNewtonOptimizer};
566

577
fn main() {
588
// init logger

examples/parking-garage.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use std::time::Instant;
2+
3+
use plotters::prelude::*;
4+
5+
use tiny_solver::helper::read_g2o;
6+
use tiny_solver::{optimizer::Optimizer, GaussNewtonOptimizer};
7+
8+
fn main() {
9+
// init logger
10+
env_logger::init();
11+
12+
let (problem, init_values) = read_g2o("tests/data/parking-garage.g2o");
13+
let init_points: Vec<(f64, f64)> = init_values.values().map(|v| (v[4], v[5])).collect();
14+
let root_drawing_area =
15+
BitMapBackend::new("parking_garage.png", (1024, 1024)).into_drawing_area();
16+
17+
root_drawing_area.fill(&WHITE).unwrap();
18+
19+
let mut scatter_ctx = ChartBuilder::on(&root_drawing_area)
20+
.x_label_area_size(40)
21+
.y_label_area_size(40)
22+
.build_cartesian_2d(-220f64..80f64, -20f64..280f64)
23+
.unwrap();
24+
scatter_ctx
25+
.configure_mesh()
26+
.disable_x_mesh()
27+
.disable_y_mesh()
28+
.draw()
29+
.unwrap();
30+
scatter_ctx
31+
.draw_series(
32+
init_points
33+
.iter()
34+
.map(|(x, y)| Circle::new((*x, *y), 2, GREEN.filled())),
35+
)
36+
.unwrap();
37+
let gn = GaussNewtonOptimizer::new();
38+
let start = Instant::now();
39+
let result = gn.optimize(&problem, &init_values, None);
40+
let duration = start.elapsed();
41+
println!("Time elapsed in total is: {:?}", duration);
42+
let result_points: Vec<(f64, f64)> = result.unwrap().values().map(|v| (v[4], v[5])).collect();
43+
scatter_ctx
44+
.draw_series(
45+
result_points
46+
.iter()
47+
.map(|(x, y)| Circle::new((*x, *y), 2, BLUE.filled())),
48+
)
49+
.unwrap();
50+
}

examples/small_problem.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::collections::HashMap;
33
use nalgebra as na;
44
use tiny_solver::{self, Optimizer};
55

6-
struct CustomFactor {}
6+
struct CustomFactor;
77
// define your own residual function and the jacobian will be auto generated
88
impl<T: na::RealField> tiny_solver::factors::Factor<T> for CustomFactor {
99
fn residual_func(&self, params: &[nalgebra::DVector<T>]) -> nalgebra::DVector<T> {
@@ -31,14 +31,14 @@ fn main() {
3131
// add residual x needs to be close to 3.0
3232
problem.add_residual_block(
3333
1,
34-
&[("x", 1)],
34+
&["x"],
3535
Box::new(tiny_solver::factors::PriorFactor {
3636
v: na::dvector![3.0],
3737
}),
3838
None,
3939
);
4040
// add custom residual for x and yz
41-
problem.add_residual_block(2, &[("x", 1), ("yz", 2)], Box::new(CustomFactor {}), None);
41+
problem.add_residual_block(2, &["x", "yz"], Box::new(CustomFactor), None);
4242

4343
// the initial values for x is 0.7 and yz is [-30.2, 123.4]
4444
let initial_values = HashMap::<String, na::DVector<f64>>::from([

examples/sphere2500.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use std::time::Instant;
2+
3+
use plotters::prelude::*;
4+
5+
use tiny_solver::helper::read_g2o;
6+
use tiny_solver::{optimizer::Optimizer, GaussNewtonOptimizer};
7+
8+
fn main() {
9+
// init logger
10+
env_logger::init();
11+
12+
let (problem, init_values) = read_g2o("tests/data/sphere2500.g2o");
13+
let init_points: Vec<(f64, f64)> = init_values.values().map(|v| (v[4], v[6])).collect();
14+
let root_drawing_area =
15+
BitMapBackend::new("sphere2500_rs.png", (1024, 1024)).into_drawing_area();
16+
17+
root_drawing_area.fill(&WHITE).unwrap();
18+
19+
let mut scatter_ctx = ChartBuilder::on(&root_drawing_area)
20+
.x_label_area_size(40)
21+
.y_label_area_size(40)
22+
.build_cartesian_2d(-60f64..60f64, -110f64..10f64)
23+
.unwrap();
24+
scatter_ctx
25+
.configure_mesh()
26+
.disable_x_mesh()
27+
.disable_y_mesh()
28+
.draw()
29+
.unwrap();
30+
scatter_ctx
31+
.draw_series(
32+
init_points
33+
.iter()
34+
.map(|(x, y)| Circle::new((*x, *y), 2, GREEN.filled())),
35+
)
36+
.unwrap();
37+
let gn = GaussNewtonOptimizer::new();
38+
let start = Instant::now();
39+
let result = gn.optimize(&problem, &init_values, None);
40+
let duration = start.elapsed();
41+
println!("Time elapsed in total is: {:?}", duration);
42+
let result_points: Vec<(f64, f64)> = result.unwrap().values().map(|v| (v[4], v[6])).collect();
43+
scatter_ctx
44+
.draw_series(
45+
result_points
46+
.iter()
47+
.map(|(x, y)| Circle::new((*x, *y), 2, BLUE.filled())),
48+
)
49+
.unwrap();
50+
}

src/factors.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use nalgebra as na;
22

3+
use crate::manifold::se3::SE3;
4+
35
pub trait Factor<T: na::RealField>: Send + Sync {
46
fn residual_func(&self, params: &[na::DVector<T>]) -> na::DVector<T>;
57
}
@@ -63,6 +65,35 @@ impl<T: na::RealField> Factor<T> for BetweenFactorSE2 {
6365
}
6466
}
6567

68+
#[derive(Debug, Clone)]
69+
pub struct BetweenFactorSE3 {
70+
pub dtx: f64,
71+
pub dty: f64,
72+
pub dtz: f64,
73+
pub dqx: f64,
74+
pub dqy: f64,
75+
pub dqz: f64,
76+
pub dqw: f64,
77+
}
78+
impl<T: na::RealField> Factor<T> for BetweenFactorSE3 {
79+
fn residual_func(&self, params: &[na::DVector<T>]) -> na::DVector<T> {
80+
let t_origin_k0 = &params[0];
81+
let t_origin_k1 = &params[1];
82+
let se3_origin_k0 = SE3::from_vec(t_origin_k0.as_view());
83+
let se3_origin_k1 = SE3::from_vec(t_origin_k1.as_view());
84+
85+
let se3_k0_k1 = SE3::from_vec(
86+
na::dvector![self.dqx, self.dqy, self.dqz, self.dqw, self.dtx, self.dty, self.dtz,]
87+
.as_view(),
88+
)
89+
.cast::<T>();
90+
91+
let se3_diff = se3_origin_k1.inverse() * se3_origin_k0 * se3_k0_k1.cast();
92+
93+
se3_diff.log()
94+
}
95+
}
96+
6697
#[derive(Debug, Clone)]
6798
pub struct PriorFactor {
6899
pub v: na::DVector<f64>,

0 commit comments

Comments
 (0)