Detailed build, dataset, experiment, and API reference for gsolver. For a quick overview and the fastest path to a first result, see the main README.
Use the outline button (top-right of this file on GitHub) to jump between sections.
gsolver/
├── gsolver/ # Core C++ library
│ ├── include/gsolver/ # Public headers
│ │ ├── core/ # GBP solver, messages
│ │ ├── graph/ # Factor graph, nodes
│ │ └── maths/ # Geometry, lie algebra
│ └── src/ # Implementation
├── python/ # Python bindings (nanobind)
├── examples/
│ ├── cpp/ # C++ experiments
│ │ ├── apps/ # Experiment executables
│ │ └── common/ # Shared utilities
│ ├── python/ # Python experiments
│ │ ├── apps/ # Experiment scripts
│ │ └── common/ # Shared utilities
│ ├── config/ # Experiment parameters
│ └── data/ # Datasets and generators
└── cmake/ # CMake configuration
The easiest way to get started is via Docker, which handles all dependencies and provides X11 forwarding for visualization.
Prerequisites:
- Docker installed
- (Optional) NVIDIA Container Toolkit for GPU acceleration
Build the image and start the container:
./docker-run.shThis script builds the gsolver Docker image (first run only) and launches the container with:
- the repository mounted at
/workspace - X11 forwarding configured for visualization
- GPU passthrough if the NVIDIA runtime is available
Once inside the container, build the project:
mkdir build && cd build
cmake .. -DGSOLVER_BUILD_EXAMPLES=ON -DGSOLVER_BUILD_PYTHON=ON
make -j$(nproc)All subsequent commands in this guide assume you are running inside the container from /workspace.
- C++17 compatible compiler (GCC 9+, Clang 10+)
- CMake 3.16+
- OpenMP (for parallel GBP)
- yaml-cpp (for configuration files)
- Python 3.8+ (for Python bindings)
On Ubuntu/Debian:
sudo apt-get install build-essential cmake libomp-dev libyaml-cpp-dev python3-dev python3-venv# Clone the repository
git clone git@github.com:rvp-group/gsolver.git
cd gsolver
# Create build directory
mkdir build && cd build
# Configure with examples and Python bindings
cmake .. -DGSOLVER_BUILD_EXAMPLES=ON -DGSOLVER_BUILD_PYTHON=ON
# Build
make -j$(nproc)CMake Options:
| Option | Default | Description |
|---|---|---|
GSOLVER_BUILD_EXAMPLES |
OFF | Build C++ example executables |
GSOLVER_BUILD_PYTHON |
OFF | Build Python bindings |
GSOLVER_BUILD_TESTS |
OFF | Build unit tests (not yet provided) |
Option 1: Install from source (recommended for development)
pip install -e .Option 2: Build wheel
pip install build
python -m build
pip install dist/gsolver-*.whlDependencies for examples:
pip install numpy pyyaml rerun-sdkThe library includes a data generator for creating synthetic trajectories with configurable noise levels.
# Build the data generator first
cd build && cmake .. -DGSOLVER_BUILD_EXAMPLES=ON && make data_generator
# Run the generator script
cd ../examples/data/generators
./generate_all_datasets.shThis creates:
datasets/synthetic/gt/- Ground truth trajectories (TUM format)datasets/synthetic/pgo/- Pose Graph Optimization datasets (g2o format)datasets/synthetic/prior/- Prior factor experiment datasets
./build/examples/cpp/data_generator \
--shape helix \
--frequency 1 \
--noise-position 0.01 \
--noise-orientation 0.001 \
--output examples/data/datasets/my_trajectory.g2oTo compute optimal qc_diag values for a trajectory, provide a ground-truth TUM file
(timestamp tx ty tz qx qy qz qw, one pose per line):
./build/examples/cpp/gp_hyperparam_trainer examples/data/datasets/synthetic/gt/sphere_100hz.tumOutput:
Suggested qc_diag for config file:
qc_diag: [0.00514381, 0.00430491, 0.00550036, 0, 0, 0.000175324]
Add these values to examples/config/experiment_params.yaml.
| Experiment | Description |
|---|---|
pgo_experiment |
Pose Graph Optimization with GP priors |
prior_experiment |
GP prior factor evaluation |
hyperparam_ablation_experiment |
Qc hyperparameter sensitivity analysis |
charuco_experiment_ba |
Bundle Adjustment on Charuco datasets |
charuco_experiment_islam |
Incremental SLAM on Charuco datasets |
stereo_visual |
Stereo visual Bundle Adjustment (single-robot and multi-robot) |
Pose Graph Optimization:
./build/examples/cpp/pgo_experiment \
examples/data/datasets/synthetic/pgo/trajectories_1hz/noise_ig1.0_n0.001_helix.g2o \
./tmp/pgo_result.tum \
helix \
-visualizePrior Experiment:
./build/examples/cpp/prior_experiment \
examples/data/datasets/synthetic/prior/trajectories_1hz/noise_ig1.0_n0.001_helix.g2o \
./tmp/prior_result.tum \
helix \
-visualizeBundle Adjustment (Charuco): the third argument is the initial-guess perturbation std (0.0 = none); the config is fixed to printing_room.
./build/examples/cpp/charuco_experiment_ba \
examples/data/datasets/charuco/printing_room_0.g2o \
./tmp/ba_result.tum \
0.0 \
-visualizeIncremental SLAM (Charuco): takes only input and output (config is fixed to printing_room).
./build/examples/cpp/charuco_experiment_islam \
examples/data/datasets/charuco/printing_room_0.g2o \
./tmp/islam_result.tum \
-visualizeHyperparameter Ablation:
./build/examples/cpp/hyperparam_ablation_experiment \
examples/data/datasets/synthetic/pgo/trajectories_1hz/noise_ig1.0_n0.001_helix.g2o \
./tmp/ablation_results/ \
helix \
-visualizeStereo Visual Bundle Adjustment — Multi-Robot:
./build/examples/cpp/stereo_visual \
examples/data/datasets/stereo_visual/multirobot/06.g2o \
examples/data/datasets/stereo_visual/gt/06.tum \
./tmp/stereo_visual_result.tum \
kitti_05 \
8 \
-visualizeStereo Visual Bundle Adjustment — Rolling Shutter:
./build/examples/cpp/stereo_visual \
examples/data/datasets/stereo_visual/rolling_shutter/kitti_06_0.1.g2o \
examples/data/datasets/stereo_visual/gt/06.tum \
./tmp/stereo_visual_result.tum \
kitti_05 \
-visualizePose Graph Optimization:
python examples/python/apps/pgo_experiment.py \
examples/data/datasets/synthetic/pgo/trajectories_1hz/noise_ig1.0_n0.001_helix.g2o \
./tmp/pgo_result.tum \
helix \
-visualizePrior Experiment:
python examples/python/apps/prior_experiment.py \
examples/data/datasets/synthetic/prior/trajectories_1hz/noise_ig1.0_n0.001_helix.g2o \
./tmp/prior_result.tum \
helix \
-visualizeBundle Adjustment (Charuco): the third argument is the initial-guess perturbation std (0.0 = none); the config is fixed to printing_room.
python examples/python/apps/charuco_experiment_ba.py \
examples/data/datasets/charuco/printing_room_0.g2o \
./tmp/ba_result.tum \
0.0 \
-visualizeIncremental SLAM (Charuco): takes only input and output (config is fixed to printing_room).
python examples/python/apps/charuco_experiment_islam.py \
examples/data/datasets/charuco/printing_room_0.g2o \
./tmp/islam_result.tum \
-visualizeMost experiments share the pattern below, with the per-experiment exceptions noted underneath:
<experiment> <input.g2o> <output.tum> <trajectory-type> [-visualize]| Argument | Description |
|---|---|
input.g2o |
Input factor graph in g2o format |
output.tum |
Output trajectory in TUM format |
trajectory-type |
Config key from experiment_params.yaml: helix, sphere, printing_room, kitti_05, … |
-visualize |
Optional: enable Rerun 3D visualization |
Exact positional arguments per experiment:
| Experiment | Positional arguments |
|---|---|
pgo_experiment, prior_experiment |
<input.g2o> <output.tum> <trajectory-type> [-visualize] |
hyperparam_ablation_experiment |
<input.g2o> <output_folder> <trajectory-type> [-visualize] (output is a folder) |
charuco_experiment_ba |
<input.g2o> <output.tum> <noise_level> [-visualize] — config fixed to printing_room; noise_level is the initial-guess perturbation std (0.0 = none) |
charuco_experiment_islam |
<input.g2o> <output.tum> [-visualize] — config fixed to printing_room |
stereo_visual |
<input.g2o> <gt.tum> <output.tum> <trajectory-type> [num_robots] [-visualize] |
gp_hyperparam_trainer |
<input.tum> |
Experiment parameters are in examples/config/experiment_params.yaml:
helix:
description: "Helical trajectory"
qc_diag: [0.00514381, 0.00430491, 0.00550036, 0, 0, 0.000175324]
num_iterations: 100qc_diag: GP motion prior hyperparameters[tx, ty, tz, rx, ry, rz]num_iterations: Number of GBP message-passing iterations
The snippets below build a tiny two-state graph, run GBP for a fixed number of
iterations, and read back the optimized pose. The available variable and factor
types are SE3PoseVel / SE3Pose / SE3Point and SE3PoseVelPrior,
SE3PoseVelPoseVel, SE3PoseVelGP, SE3PoseVelPose, SE3PoseVelPoint (plus
SE3PoseVelStereoPoint in C++). For complete, runnable programs see
examples/cpp/apps/ and
examples/python/apps/, with shared graph-building
helpers in examples/{cpp,python}/common/factor_graph_builder.
#include <gsolver/core/gbp_solver.h>
#include <gsolver/graph/factor_graph.h>
#include <gsolver/graph/types/variables/se3_posevel.h>
#include <gsolver/graph/types/factors/se3_posevel_posevel.h>
#include <gsolver/graph/types/factors/se3_posevel_prior.h>
using namespace gsolver;
FactorGraph graph;
// Two pose + velocity states, initialized at the identity
SE3PoseVelValue mu;
mu.pose = Eigen::Isometry3d::Identity();
mu.vel = Eigen::Vector<double, 6>::Zero();
Eigen::Matrix<double, 12, 12> sigma = Eigen::Matrix<double, 12, 12>::Identity();
auto x0 = std::make_shared<SE3PoseVel>("x0", mu, sigma, 0.0);
auto x1 = std::make_shared<SE3PoseVel>("x1", mu, sigma, 1.0);
graph.addVariableNode(x0);
graph.addVariableNode(x1);
// Anchor the first state with a prior at the origin
Eigen::Matrix<double, 6, 6> prior_cov = Eigen::Matrix<double, 6, 6>::Identity() * 1e-3;
auto prior = std::make_shared<SE3PoseVelPrior>("prior_x0", Eigen::Isometry3d::Identity(), prior_cov);
graph.addFactorNode(prior, std::vector<std::string>{"x0"});
// Relative motion measurement: 1 m along x between x0 and x1
Eigen::Isometry3d z = Eigen::Isometry3d::Identity();
z.translation().x() = 1.0;
Eigen::Matrix<double, 6, 6> odom_cov = Eigen::Matrix<double, 6, 6>::Identity() * 1e-2;
auto odom = std::make_shared<SE3PoseVelPoseVel>("odom_x0_x1", z, odom_cov);
graph.addFactorNode(odom, std::vector<std::string>{"x0", "x1"});
// Run Gaussian Belief Propagation for a fixed number of iterations
GbpSolver solver(SolverScheduleType::SYNCHRONOUS);
for (int i = 0; i < 50; ++i)
solver.performIteration(graph);
// Read the optimized state (translation -> (1, 0, 0))
auto x1_node = std::static_pointer_cast<SE3PoseVel>(graph.getVariableNode("x1"));
Eigen::Isometry3d x1_pose = x1_node->mu_.pose;import numpy as np
from gsolver import (
FactorGraph, GbpSolver, SolverScheduleType,
SE3PoseVel, SE3PoseVelValue, SE3PoseVelPrior, SE3PoseVelPoseVel,
)
fg = FactorGraph()
# Two pose + velocity states, initialized at the identity
def identity_state():
s = SE3PoseVelValue()
s.pose = np.eye(4)
s.vel = np.zeros(6)
return s
sigma12 = np.eye(12)
fg.add_variable(SE3PoseVel("x0", identity_state(), sigma12, 0.0))
fg.add_variable(SE3PoseVel("x1", identity_state(), sigma12, 1.0))
# Anchor the first state with a prior at the origin
fg.add_factor(SE3PoseVelPrior("prior_x0", np.eye(4), np.eye(6) * 1e-3), ["x0"])
# Relative motion measurement: 1 m along x between x0 and x1
z = np.eye(4); z[0, 3] = 1.0
fg.add_factor(SE3PoseVelPoseVel("odom_x0_x1", z, np.eye(6) * 1e-2), ["x0", "x1"])
# Run Gaussian Belief Propagation for a fixed number of iterations
solver = GbpSolver(SolverScheduleType.SYNCHRONOUS)
for _ in range(50):
solver.perform_iteration(fg)
# Read the optimized state (translation -> (1, 0, 0))
optimized_pose = fg.get_variable("x1").mu.poseThe library integrates with Rerun for real-time 3D visualization.
-
Install the Rerun viewer:
pip install rerun-sdk rerun
-
Run any experiment with
-visualizeflag -
The viewer will show:
- Trajectory as 3D points and coordinate frames
- Factor graph structure
- Optimization progress (if enabled)
Standard g2o format with SE3 poses and edges:
VERTEX_SE3:QUAT id x y z qx qy qz qw
EDGE_SE3:QUAT id1 id2 dx dy dz dqx dqy dqz dqw info_11 info_12 ... info_66
Standard TUM trajectory format:
timestamp tx ty tz qx qy qz qw
Additional datasets are documented in
examples/data/datasets/stereo_visual/README.md.