# cartographer/mapping/internal/constraints/constraint_builder.h ## Overview `constraint_builder.h` provides the shared options factory for the pose graph's loop-closure machinery. The actual constraint computation is performed asynchronously by the concrete classes in the same directory: `ConstraintBuilder2D` and `ConstraintBuilder3D`. Both are thread-safe classes that schedule background work on a `ThreadPoolInterface` to find constraints (loop closures) between submaps and nodes. ## Options Factory ```cpp proto::ConstraintBuilderOptions CreateConstraintBuilderOptions( common::LuaParameterDictionary* parameter_dictionary); ``` Reads `sampling_ratio`, `max_constraint_distance`, `min_score`, `global_localization_min_score`, `loop_closure_translation_weight`, `loop_closure_rotation_weight`, and `log_matches`, plus the nested `fast_correlative_scan_matcher` and `ceres_scan_matcher` dictionaries (2D) and `fast_correlative_scan_matcher_3d` and `ceres_scan_matcher_3d` dictionaries (3D). ## ConstraintBuilder2D / ConstraintBuilder3D Both classes expose the same interface. The 2D variant matches `Submap2D` grids with `FastCorrelativeScanMatcher2D` + `CeresScanMatcher2D`; the 3D variant matches `Submap3D` hybrid grids with `FastCorrelativeScanMatcher3D` + `CeresScanMatcher3D`. ### Construction ```cpp ConstraintBuilder2D(const proto::ConstraintBuilderOptions& options, common::ThreadPoolInterface* thread_pool); ConstraintBuilder3D(const proto::ConstraintBuilderOptions& options, common::ThreadPoolInterface* thread_pool); ``` ### Public API ```cpp using Constraint = PoseGraphInterface::Constraint; using Result = std::vector; // Schedule a constraint search between a submap and a node, using an // 'initial_relative_pose' (relative to the submap) to seed the search. void MaybeAddConstraint(const SubmapId& submap_id, const Submap2D* submap, const NodeId& node_id, const TrajectoryNode::Data* const constant_data, const transform::Rigid2d& initial_relative_pose); // Schedule a full-submap (global localization) match, without a pose prior. void MaybeAddGlobalConstraint(const SubmapId& submap_id, const Submap2D* submap, const NodeId& node_id, const TrajectoryNode::Data* const constant_data); void NotifyEndOfNode(); void WhenDone(const std::function& callback); int GetNumFinishedNodes(); void DeleteScanMatcher(const SubmapId& submap_id); static void RegisterMetrics(metrics::FamilyFactory* family_factory); ``` ### Usage Pattern The intended interleaving is: call `MaybeAddConstraint` / `MaybeAddGlobalConstraint` and `NotifyEndOfNode` any number of times, then call `WhenDone` once. When all background computations finish, the `callback` is executed (in the thread pool) with the resulting constraints, after which another cycle can begin. The pointees of `submap` and `constant_data` must remain valid until all computations are finished. ### Background Pipeline 1. Per submap, a `SubmapScanMatcher` is built lazily on a background thread. For 2D it holds the submap `Grid2D` and a `FastCorrelativeScanMatcher2D`; for 3D it holds the hybrid grids and a `FastCorrelativeScanMatcher3D`. Construction tasks must complete before the grid/matcher is accessed. 2. `ComputeConstraint` (running in the thread pool) first filters candidates: a `FixedRatioSampler` applies `sampling_ratio`, and nodes farther than `max_constraint_distance` from the submap are skipped. 3. The fast correlative matcher proposes candidate poses above `min_score` (or `global_localization_min_score` for `MaybeAddGlobalConstraint`), and the Ceres matcher refines the best candidate. 4. If the refined match scores above `min_score`, a `Constraint` is added to the results, weighted by `loop_closure_translation_weight` and `loop_closure_rotation_weight`. 5. `NotifyEndOfNode` increments the finished-node counter; `WhenDone` fires once all scheduled work is complete. ## Options `proto::ConstraintBuilderOptions` (from `pose_graph/constraint_builder_options.proto`): | Field | Type | Meaning | |-------|------|---------| | `sampling_ratio` | double | Fraction of nodes sampled per submap for constraint search | | `max_constraint_distance` | double | Maximum node-to-submap distance considered | | `min_score` | double | Minimum match score to accept a constraint | | `global_localization_min_score` | double | Minimum score for full-submap matches | | `loop_closure_translation_weight` | double | Translation weight of accepted constraints | | `loop_closure_rotation_weight` | double | Rotation weight of accepted constraints | | `log_matches` | bool | Log accepted matches | | `fast_correlative_scan_matcher` / `_3d` | message | Options for the candidate matcher | | `ceres_scan_matcher` / `_3d` | message | Options for the refinement matcher | ## Configuration From `pose_graph.lua`: ```lua constraint_builder = { sampling_ratio = 0.3, max_constraint_distance = 15., min_score = 0.55, global_localization_min_score = 0.66, loop_closure_translation_weight = 1.1e4, loop_closure_rotation_weight = 1e5, log_matches = true, fast_correlative_scan_matcher = { ... }, ceres_scan_matcher = { ... }, fast_correlative_scan_matcher_3d = { ... }, ceres_scan_matcher_3d = { ... }, } ``` ## Role in the SLAM Pipeline The pose graph periodically triggers constraint building as submaps are finished and new nodes are added. The constraints produced by the constraint builder are fed into the optimization problem (see `optimization_problem_interface.md`), which minimizes their residuals to update global poses. ## See Also - [OptimizationProblemInterface](../optimization/optimization_problem_interface.md) — Consumes the produced constraints - [FastCorrelativeScanMatcher2D](../2d/scan_matching/fast_correlative_scan_matcher_2d.md) — Candidate matcher (2D) - [FastCorrelativeScanMatcher3D](../3d/scan_matching/fast_correlative_scan_matcher_3d.md) — Candidate matcher (3D) - [PoseGraphInterface](../../pose_graph_interface.md) — Constraint definition and pose graph roles