Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Documentation #27

Merged
merged 9 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,15 @@ members = ["factrs-bench", "factrs-proc"]
exclude = ["factrs-typetag"]

[package.metadata.docs.rs]
rustdoc-args = ["--html-in-header", "assets/katex-header.html"]
features = ["serde", "rerun"]
rustdoc-args = [
"--cfg",
"docsrs",
"--html-in-header",
"assets/katex-header.html",
]
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]


[dependencies]
foldhash = "0.1.3"
Expand Down Expand Up @@ -93,3 +101,4 @@ opt-level = 3
[[example]]
name = "serde"
required-features = ["serde"]
doc-scrape-examples = true
59 changes: 43 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
# factrs
# fact.rs

[![crates.io](https://img.shields.io/crates/v/factrs.svg)](https://crates.io/crates/factrs)
[![minimum rustc 1.81](https://img.shields.io/badge/rustc-1.81+-red.svg)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html)
[![crate](https://img.shields.io/crates/v/factrs.svg)](https://crates.io/crates/factrs)
[![doc](https://docs.rs/factrs/badge.svg)](https://docs.rs/factrs)
[![ci](https://github.com/rpl-cmu/factrs/actions/workflows/ci.yml/badge.svg)](https://github.com/rpl-cmu/factrs/actions/workflows/ci.yml)
[![docs.rs](https://docs.rs/factrs/badge.svg)](https://docs.rs/factrs)

factrs is a nonlinear least squares optimization library over factor graphs written in Rust.
fact.rs (pronounced factors) is a nonlinear least squares optimization library over factor graphs written in Rust.

It is specifically geared toward sensor fusion in robotics. It aims to be fast, easy to use, and safe. The factrs API takes heavy inspiration from the [gtsam library](https://gtsam.org/).
It is specifically geared toward sensor fusion in robotics. It aims to be fast, easy to use, and safe. The fact.rs API takes heavy inspiration from the [gtsam library](https://gtsam.org/).

Currently, it supports the following features
- Gauss-Newton & Levenberg-Marquadt Optimizers
- Common Lie Groups supported (SO2, SO3, SE2, SE3) with optimization in Lie
Algebras
- Pose graph optimization and IMU preintegration
- Automatic differentiation via dual numbers
- First class support for robust kernels
- Serialization of graphs & variables via optional serde support
- Easy conversion to rerun types for straightforward visualization

We recommend you checkout the [docs](https://docs.rs/factrs/latest/factrs/) for more info.
We recommend you checkout the [docs](https://docs.rs/factrs/latest/factrs/) for more info. For usage, simply add factrs to your `Cargo.toml` and start using it!

# Examples
There's a number of examples found in the [examples](/examples/) folder, including loading g20 files, serialization, and custom factors.
Expand All @@ -31,11 +30,11 @@ to visualize the optimization steps with [rerun](https://rerun.io) simply add `-

Running the other examples can be done similarly,
```bash
cargo run --release --example serde --features serde
cargo run --release --example gps
cargo run --release --example serde --features serde
```

Additionally, we recommend checking out the [tests](/tests/) folder for more examples of custom noise models, residuals, robust kernels, and variables.
Additionally, we recommend checking out the [tests](/tests/) folder for more examples of how to make custom noise models, residuals, robust kernels, and variables.

<details>
<summary>Full Example</summary>
Expand Down Expand Up @@ -68,12 +67,6 @@ fn main() {

let res = BetweenResidual::new(y.minus(&x));
let factor = fac![res, (X(0), X(1)), 0.1 as std, Huber::default()];
// fac! is syntactic sugar for the following
// let noise = GaussianNoise::from_scalar_sigma(0.1);
// let factor = FactorBuilder::new2(res, X(0), X(1))
// .noise(GaussianNoise::from_scalar_sigma(0.1))
// .robust(Huber::default())
// .build();
graph.add_factor(factor);

// Optimize!
Expand All @@ -83,6 +76,40 @@ fn main() {
}
```
</details>
</br>

# Compile-time Errors

fact.rs leans into the Rust way of doing things, and attempts to compile-time error as much as possible. This includes the following,
- Symbols are assigned to variables at compile-time, ensuring that symbols are can not be mismatched
- Enforcing the correct number of keys for a factor
- Ensuring that noise model dimensions match the residual dimensions

A few examples,
```rust
use factrs::core::{assign_symbols, fac, PriorResidual, Values, VectorVar2, SO2};

// Assign symbols to variable types
assign_symbols(X: SO2, Y: SO2);
let mut values = Values::new();

// Proper usage
let id = SO2::identity();
values.insert(X(0), id);
let prior = PriorResidual::new(id);
let f = fac![prior, X(0), (0.1, 0.2) as std];

// These will all compile-time error
// mismatched symbol-variable types
values.insert(X(5), VectorVar2::identity());
// wrong number of keys
let f = fac![PriorResidual::new(id), (X(0), X(1))];
// wrong noise-model dimension
let n = GaussianNoise::<5>::from_scalar_sigma(0.1);
let f = fac![PriorResidual::new(id), X(0), n];
// mismatched symbol-variable types
let f = fac![PriorResidual::new(id), Y(0), 0.1 as std];
```

# Benchmarks
Performance-wise, factrs is competitive with alternative libraries. Benchmarks were ran on a 12th Gen Intel i9 and are all single-threaded (for now). Current benchmarks include [gtsam](https://github.com/borglab/gtsam/) and [tinysolver-rs](https://github.com/powei-lin/tiny-solver-rs).
Expand Down
28 changes: 16 additions & 12 deletions examples/gps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ A simple 2D pose slam example with "GPS" measurements
#![allow(unused_imports)]
// Our state will be represented by SE2 -> theta, x, y
// VectorVar2 is a newtype around Vector2 for optimization purposes
use factrs::variables::{VectorVar2, SE2};
use factrs::{
assign_symbols,
core::{BetweenResidual, GaussNewton, Graph, Values},
dtype, fac,
linalg::{Const, ForwardProp, Numeric, NumericalDiff, VectorX},
residuals::Residual1,
traits::*,
variables::{VectorVar2, SE2},
};

#[derive(Clone, Debug)]
Expand All @@ -43,7 +43,8 @@ impl Residual1 for GpsResidual {
// Use forward propagation for differentiation
type Differ = ForwardProp<<Self as Residual1>::DimIn>;
// Alternatively, could use numerical differentiation (6 => 10^-6 as
// denominator) type Differ = NumericalDiff<6>;
// denominator)
// type Differ = NumericalDiff<6>;

// The input variable type, input dimension of variable(s), and output dimension
// of residual
Expand Down Expand Up @@ -75,8 +76,8 @@ impl Residual1 for GpsResidual {
// }
// }
// As a note - the above jacobian is only valid if running with the "left"
// feature disabled Switching to the left feature will change the jacobian
// used
// feature disabled
// Enabling the left feature will change the jacobian
}

// Here we assign X to always represent SE2 variables
Expand Down Expand Up @@ -107,14 +108,17 @@ fn main() {
values.insert(X(1), SE2::identity());
values.insert(X(2), SE2::identity());

// These will all compile-time error
// values.insert(X(5), VectorVar2::identity()); // wrong variable type
// let f = fac![GpsResidual::new(0.0, 0.0), (X(0), X(1))]; // wrong number of
// keys let n = GaussianNoise::<5>::from_scalar_sigma(0.1);
// let f = fac![GpsResidual::new(0.0, 0.0), X(0), n]; // wrong noise-model
// dimension assign_symbols!(Y : VectorVar2);
// let f = fac![GpsResidual::new(0.0, 0.0), Y(0), 0.1 as std]; // wrong variable
// type
// // These will all compile-time error
// // mismatched symbol-variable types
// values.insert(X(5), VectorVar2::identity());
// // wrong number of keys
// let f = fac![GpsResidual::new(0.0, 0.0), (X(0), X(1))];
// // wrong noise-model dimension
// let n = factrs::noise::GaussianNoise::<5>::from_scalar_sigma(0.1);
// let f = fac![GpsResidual::new(0.0, 0.0), X(0), n];
// // mismatched symbol-variable types
// assign_symbols!(Y : VectorVar2);
// let f = fac![GpsResidual::new(0.0, 0.0), Y(0), 0.1 as std];

// optimize
let mut opt: GaussNewton = GaussNewton::new(graph);
Expand Down
6 changes: 0 additions & 6 deletions examples/readme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,6 @@ fn main() {

let res = BetweenResidual::new(y.minus(&x));
let factor = fac![res, (X(0), X(1)), 0.1 as std, Huber::default()];
// fac! is syntactic sugar for the following
// let noise = GaussianNoise::from_scalar_sigma(0.1);
// let factor = FactorBuilder::new2(res, X(0), X(1))
// .noise(GaussianNoise::from_scalar_sigma(0.1))
// .robust(Huber::default())
// .build();
graph.add_factor(factor);

// Optimize!
Expand Down
3 changes: 1 addition & 2 deletions factrs-proc/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use syn::parse_macro_input;
use syn::ItemImpl;
use syn::{parse_macro_input, ItemImpl};

mod fac;
mod noise;
Expand Down
5 changes: 2 additions & 3 deletions factrs-proc/src/noise.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use proc_macro2::Ident;
use proc_macro2::TokenStream as TokenStream2;
use proc_macro2::{Ident, TokenStream as TokenStream2};
use quote::quote;
use syn::{GenericParam, ItemImpl, Type, TypePath};

Expand Down Expand Up @@ -40,7 +39,7 @@ pub fn mark(item: ItemImpl) -> TokenStream2 {
// If one generic and it's const, do first 20
1 => {
if let GenericParam::Const(_) = item.generics.params.first().unwrap() {
for i in 1usize..=20 {
for i in 1usize..=32 {
let name_str = format!("{}<{}>", name, i);
expanded.extend(quote!(
typetag::__private::inventory::submit! {
Expand Down
2 changes: 1 addition & 1 deletion factrs-proc/src/residual.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub fn mark(mut item: ItemImpl) -> TokenStream2 {
let residual_values = format_ident!("residual{}_values", num);
let residual_jacobian = format_ident!("residual{}_jacobian", num);

// If we should add typetag
// If we should add typetag::Tagged to the generic bounds
let typetag = if cfg!(feature = "serde") {
// Add where clauses to all impl
let all_type_params: Vec<_> = item.generics.type_params().cloned().collect();
Expand Down
30 changes: 2 additions & 28 deletions factrs-proc/src/variable.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
use proc_macro2::Ident;
use proc_macro2::TokenStream as TokenStream2;
use proc_macro2::{Ident, TokenStream as TokenStream2};
use quote::{quote, ToTokens};
use syn::parse_quote;
use syn::ConstParam;
use syn::Error;
use syn::GenericParam;
use syn::{ItemImpl, Type, TypePath};
use syn::{Error, ItemImpl, Type, TypePath};

fn type_name(mut ty: &Type) -> Option<Ident> {
loop {
Expand Down Expand Up @@ -56,28 +51,7 @@ pub fn mark(item: ItemImpl) -> TokenStream2 {
}
));
}
// With a single const generic
2 => {
let first_generic = item.generics.params.first().unwrap();
if let GenericParam::Const(ConstParam { ident, .. }) = first_generic {
let format = format!("{}<{{}}>", name);
// let format = quote! { #name<{}> }.to_string();
expanded.extend(quote! {
impl<const #ident: usize> typetag::Tagged for #name<#ident> {
fn tag() -> String {
format!(#format, #ident)
}
}
});
for i in 1usize..=20 {
let name_str = format!("{}<{}>", name, i);
let name_qt = parse_quote!(#name<#i>);
expanded.extend(tag_all(&name_qt, &name_str));
}
}
}
// Anymore and it's up to the user
// TODO: Could at least implement tagged here
_ => {}
}

Expand Down
2 changes: 1 addition & 1 deletion factrs-typetag
7 changes: 5 additions & 2 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ profile:
cargo flamegraph --profile profile --example g2o -- ./examples/data/parking-garage.g2o

# build docs with latex support
docs:
RUSTDOCFLAGS="--html-in-header $PWD/assets/katex-header.html" cargo doc
doc:
RUSTDOCFLAGS="--cfg docsrs --html-in-header $PWD/assets/katex-header.html" cargo doc --features="serde rerun" -Zunstable-options -Zrustdoc-scrape-examples

bacon-doc:
RUSTDOCFLAGS="--cfg docsrs --html-in-header $PWD/assets/katex-header.html" bacon doc --features="serde rerun" -- -Zunstable-options -Zrustdoc-scrape-examples

# ---------------------- Easton specific helpers that work on my system ---------------------- #
# tune the system for benchmarking using pyperf
Expand Down
5 changes: 3 additions & 2 deletions src/containers/factor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ use crate::{
/// - <blue>Robust Kernel</blue>: The robust kernel weights the error of the
/// factor, given by the traits in the [robust](crate::robust) module.
///
/// To construct a factor, please see the [FactorBuilder] struct.
/// The easiest way to construct a factor is using the [fac](factrs::fac) macro,
/// or alternatively, using [FactorBuilder].
///
/// During optimization the factor is linearized around a set of values into a
/// [LinearFactor].
Expand Down Expand Up @@ -123,7 +124,7 @@ impl fmt::Debug for Factor {
/// Formatter for a factor
///
/// Specifically, this can be used if custom symbols are desired. See
/// `tests/custom_key` for examples.
/// [tests/custom_key](https://github.com/rpl-cmu/factrs/blob/dev/tests/custom_key.rs) for examples.
pub struct FactorFormatter<'f, KF> {
factor: &'f Factor,
kf: PhantomData<KF>,
Expand Down
2 changes: 1 addition & 1 deletion src/containers/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ impl Debug for Graph {
/// Formatter for a graph
///
/// Specifically, this can be used if custom symbols are desired. See
/// `tests/custom_key` for examples.
/// [tests/custom_key](https://github.com/rpl-cmu/factrs/blob/dev/tests/custom_key.rs) for examples.
pub struct GraphFormatter<'g, KF> {
graph: &'g Graph,
kf: PhantomData<KF>,
Expand Down
22 changes: 14 additions & 8 deletions src/containers/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,28 @@ pub struct Key(pub u64);

impl Symbol for Key {}

/// This provides a custom conversion to a [Key].
/// Human-readable symbol that will be turned into a [Key]
///
/// This is a requirement to be inserted into Values. Examples are
/// X(100), A(101), etc.
pub trait Symbol: fmt::Debug + Into<Key> {}

/// Adds type information to a symbol
/// Adds type information to a [Symbol]
///
/// Will almost always be generated by [assign_symbols]
/// Will almost always be generated by [assign_symbols](factrs::assign_symbols)
pub trait TypedSymbol<V: VariableDtype>: Symbol {}

/// Custom formatting for keys in [Values](factrs::containers::Values) or [Graph](factrs::containers::Graph)
/// Custom formatting for keys in [Values](factrs::containers::Values) or
/// [Graph](factrs::containers::Graph)
///
/// The average user will likely never have to implement this as [DefaultSymbolHandler] will almost always be sufficient,
/// unless a custom key is used.
/// The average user will likely never have to implement this as
/// [DefaultSymbolHandler] will almost always be sufficient, unless a custom key
/// is used.
pub trait KeyFormatter {
fn fmt(f: &mut dyn Write, key: Key) -> fmt::Result;
}

// ------------------------- Basic single char symbol ------------------------- //
// --------------------- Basic single char symbol --------------------- //

// Char is stored in the high CHR_BITS
// Idx is stored in the low IDX_BITS
Expand All @@ -47,6 +49,9 @@ const IDX_SIZE: usize = TOTAL_SIZE - CHR_SIZE;
const CHR_MASK: u64 = (char::MAX as u64) << IDX_SIZE;
const IDX_MASK: u64 = !CHR_MASK;

/// Default symbol handler for [Symbols](Symbol)
///
/// Specifically, this converts a char and an index into a [Key] and back
#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DefaultSymbolHandler;
Expand Down Expand Up @@ -74,7 +79,8 @@ impl KeyFormatter for DefaultSymbolHandler {
}
}

/// Creates and assigns symbols to variables
/// Creates and assigns [Symbols](Symbol) to
/// [Variables](factrs::variables::Variable)
///
/// To reduce runtime errors, factrs symbols are tagged
/// with the type they will be used with. This macro will create a new symbol
Expand Down
Loading
Loading