Problem Construction
Variables and keys
Variables are stored in a
slotmap-backed arena. add_variable returns a
stable, generational VarKey; keep the key and pass it wherever a factor needs
the variable. There are no string lookups on the hot path.
#![allow(unused)]
fn main() {
use apex_solver::{ManifoldType};
use nalgebra::dvector;
let pose = problem.add_variable(ManifoldType::SE3,
dvector![tx, ty, tz, qw, qx, qy, qz]);
let point = problem.add_variable(ManifoldType::RN, dvector![x, y, z]);
}
Supported manifold types: SO2, SO3, SE2, SE3, SE23, SGal3, Sim3,
RN. Parameters are stored in the manifold’s representation size (e.g. 7
doubles for SE(3)); optimization happens in the tangent space (6 DOF for
SE(3)) via a right-perturbation retraction.
That distinction runs through everything downstream: Jacobians have tangent width, the step is applied with rather than addition, and the linear system is built in tangent coordinates. See From Factor Graph to Linear System.
Residual blocks
A residual block binds one factor to the variables it reads:
#![allow(unused)]
fn main() {
problem.add_residual_block(&[k_from, k_to], Box::new(between_factor), loss);
}
- The factor implements
Factor::linearize(params, residual, jacobian)— see Factors. lossis an optionalBox<dyn LossFunction>; see Robust Loss Functions.- To weight the block by its measurement uncertainty, use
add_residual_block_with_noiseand pass aNoiseModel. Without one the block is unweighted, which is only correct when every residual in the graph is already in comparable units. - Use
try_add_residual_blockfor aResult-returning registration that runs the factor’svalidate_variableshook — shape mismatches are caught at registration time instead of during parallel evaluation.
Gauge freedom
A pose graph is unconstrained up to a global rigid transform. Anchor it by fixing DOFs of one pose:
#![allow(unused)]
fn main() {
for dof in 0..6 {
problem.fix_variable(pose_keys[0], dof);
}
}
Alternatively, register a PriorFactor on the first pose.
Schur landmarks
Schur-complement solvers eliminate landmark blocks. Mark the variables to eliminate:
#![allow(unused)]
fn main() {
problem.mark_for_elimination(point_key);
}
Every Schur solver (ExplicitSparseSchur, ExplicitDenseSchur,
ImplicitSparseSchur) can also classify automatically from manifold type and
size when the ordering is explicitly opted in:
#![allow(unused)]
fn main() {
use apex_solver::linalg::SchurOrdering;
let ordering = SchurOrdering::default().with_auto_detect(true);
}
Auto-detection is off by default: Rn(3) is ambiguous between 3-D
landmarks and self-calibration intrinsics ([focal, k1, k2]), and eliminating
intrinsics as landmarks silently corrupts the Schur complement.
What elimination buys, and which Schur solver to pick, is in Linear Solvers & the Schur Complement.