# CeresSharp - AI Agent Conversion Guide ## πŸ“‹ Overview **CeresSharp** is a C# wrapper library for Ceres Solver 2.2.0, providing a high-level API that closely mirrors the C++ API. This guide is designed for **AI Agents** to help convert C++ Ceres code to C# using CeresSharp. **Target Framework**: .NET 10.0 **Platform**: Linux only (`libceres_wrapper.so`) **Status**: βœ… **100% Complete** - All 220+ APIs implemented (including AutoDiffManifold) --- ## 🎯 Quick Reference: C++ β†’ C# Mapping ### Namespace Mapping | C++ | C# | |-----|-----| | `ceres::Problem` | `CeresSharp.Problem` | | `ceres::Solver::Options` | `CeresSharp.SolverOptions` | | `ceres::Solver::Summary` | `CeresSharp.SolverSummary` | | `ceres::CostFunction` | `CeresSharp.CostFunction` (base) | | `ceres::AutoDiffCostFunction` | `CeresSharp.AutoDiffCostFunction` | | `ceres::LossFunction` | `CeresSharp.LossFunction` (base) | | `ceres::HuberLoss` | `CeresSharp.HuberLoss` | | `ceres::QuaternionManifold` | `CeresSharp.QuaternionManifold` | ### Using Statements ```csharp using CeresSharp; using CeresSharp.Enums; using CeresSharp.Advanced; // For advanced features ``` --- ## πŸ”„ Conversion Patterns ### 1. Problem Creation **C++:** ```cpp ceres::Problem problem; ``` **C#:** ```csharp using var problem = new Problem(); // or var problem = new Problem(); // Remember to dispose: problem.Dispose(); ``` **Key Differences:** - C# uses `using` statement for automatic disposal - All objects implement `IDisposable` for proper resource cleanup - **IMPORTANT**: Objects added to Problem (cost functions, loss functions, manifolds) are owned by Problem and should NOT be manually disposed --- ### 2. Adding Parameter Blocks **C++:** ```cpp double parameters[2] = {1.0, 2.0}; problem.AddParameterBlock(parameters, 2); ``` **C#:** ```csharp var parameters = new double[] { 1.0, 2.0 }; problem.AddParameterBlock(parameters, parameters.Length); ``` **Key Differences:** - C# uses managed arrays (`double[]`) - Arrays are automatically pinned when passed to native code - No need for manual memory management --- ### 3. Cost Functions #### AutoDiffCostFunction **C++:** ```cpp struct CostFunctor { template bool operator()(const T* const x, T* residual) const { residual[0] = x[0] - 1.0; return true; } }; auto* cost_function = new ceres::AutoDiffCostFunction( new CostFunctor); ``` **C#:** ```csharp var costFunction = new AutoDiffCostFunction( (parameters, residuals) => { residuals[0] = parameters[0][0] - 1.0; return true; }, numResiduals: 1, parameterBlockSizes: new[] { 1 }); ``` **Key Differences:** - C# uses lambda expressions instead of functors - Parameters are passed as `double[][]` (array of parameter blocks) - Residuals are passed as `double[]` (output array) - Return `true` on success, `false` on failure #### Multiple Parameter Blocks **C++:** ```cpp struct CostFunctor { template bool operator()(const T* const x, const T* const y, T* residual) const { residual[0] = x[0] * y[0] - 1.0; return true; } }; auto* cost_function = new ceres::AutoDiffCostFunction( new CostFunctor); ``` **C#:** ```csharp var costFunction = new AutoDiffCostFunction( (parameters, residuals) => { // parameters[0] is first block (size 2) // parameters[1] is second block (size 3) residuals[0] = parameters[0][0] * parameters[1][0] - 1.0; return true; }, numResiduals: 1, parameterBlockSizes: new[] { 2, 3 }); ``` --- ### 4. Loss Functions **C++:** ```cpp ceres::HuberLoss loss(1.0); problem.AddResidualBlock(cost_function, &loss, parameters); ``` **C#:** ```csharp using var loss = new HuberLoss(1.0); var residualBlockId = problem.AddResidualBlock( costFunction, loss, parameterBlocks: new[] { parameters }); // ⚠️ IMPORTANT: Problem now owns both costFunction and loss // Don't manually dispose them - Problem will cleanup automatically ``` **Available Loss Functions:** - `TrivialLoss()` - No loss (equivalent to `null` in C#) - `HuberLoss(double a)` - `CauchyLoss(double a)` - `SoftLOneLoss(double a)` - `ArctanLoss(double a)` - `TolerantLoss(double a, double b)` --- ### 5. Manifolds **C++:** ```cpp ceres::QuaternionManifold* manifold = new ceres::QuaternionManifold(); problem.SetManifold(parameters, manifold); ``` **C#:** ```csharp using var manifold = new QuaternionManifold(); problem.SetManifold(parameters, manifold); // ⚠️ IMPORTANT: Problem now owns the manifold // Don't manually dispose it - Problem will cleanup automatically ``` <|tool▁calls▁begin|><|tool▁call▁begin|> read_file **Available Manifolds:** - `QuaternionManifold()` - For 3D rotations - `SphereManifold(int dimension)` - `LineManifold(int dimension)` - `EuclideanManifold(int dimension)` - `SubsetManifold(int[] constantSubset, int ambientSize)` - `ProductManifold(Manifold[] manifolds)` - `AutoDiffManifold(int ambientSize, int tangentSize, PlusOperation plus, MinusOperation minus)` ⭐ **NEW** - Custom manifolds via callbacks (replacement for AutoDiffLocalParameterization) --- ### 6. Solver Options **C++:** ```cpp ceres::Solver::Options options; options.linear_solver_type = ceres::DENSE_QR; options.max_num_iterations = 100; options.function_tolerance = 1e-6; ``` **C#:** ```csharp using var options = new SolverOptions { LinearSolverType = LinearSolverType.DenseQr, MaxNumIterations = 100, FunctionTolerance = 1e-6 }; ``` **Enum Mapping:** | C++ | C# | |-----|-----| | `ceres::DENSE_QR` | `LinearSolverType.DenseQr` | | `ceres::SPARSE_NORMAL_CHOLESKY` | `LinearSolverType.SparseNormalCholesky` | | `ceres::TRUST_REGION` | `MinimizerType.TrustRegion` | | `ceres::LINE_SEARCH` | `MinimizerType.LineSearch` | **Using Constants:** ```csharp using CeresSharp; var options = new SolverOptions { MaxNumIterations = Constants.DefaultMaxNumIterations, FunctionTolerance = Constants.DefaultFunctionTolerance, GradientTolerance = Constants.DefaultGradientTolerance }; ``` --- ### 7. Solving **C++:** ```cpp ceres::Solver::Summary summary; ceres::Solve(options, &problem, &summary); ``` **C#:** ```csharp using var summary = problem.Solve(options); ``` **Key Differences:** - C# returns `SolverSummary` directly - No need to pass summary as parameter - Summary is automatically created and returned --- ### 8. Accessing Results **C++:** ```cpp std::cout << summary.termination_type << std::endl; std::cout << summary.final_cost << std::endl; std::cout << summary.iterations << std::endl; ``` **C#:** ```csharp Console.WriteLine($"Termination: {summary.TerminationType}"); Console.WriteLine($"Final cost: {summary.FinalCost}"); Console.WriteLine($"Iterations: {summary.Iterations}"); ``` **Key Differences:** - C# uses properties (PascalCase) instead of fields (snake_case) - Enum types are strongly typed --- ### 9. Callbacks #### Iteration Callback **C++:** ```cpp struct IterationCallback : public ceres::IterationCallback { ceres::CallbackReturnType operator()( const ceres::IterationSummary& summary) override { std::cout << "Iteration: " << summary.iteration << std::endl; return ceres::SOLVER_CONTINUE; } }; options.callbacks.push_back(new IterationCallback); ``` **C#:** ```csharp options.SetIterationCallback(summary => { Console.WriteLine($"Iteration: {summary.Iterations}"); return true; // Continue (false to stop) }); ``` --- ### 10. Parameter Bounds **C++:** ```cpp problem.SetParameterLowerBound(parameters, 0, 0.0); problem.SetParameterUpperBound(parameters, 0, 10.0); ``` **C#:** ```csharp problem.SetParameterLowerBound(parameters, index: 0, lowerBound: 0.0); problem.SetParameterUpperBound(parameters, index: 0, upperBound: 10.0); ``` --- ### 11. Setting Parameter Blocks Constant/Variable **C++:** ```cpp problem.SetParameterBlockConstant(parameters); problem.SetParameterBlockVariable(parameters); ``` **C#:** ```csharp problem.SetParameterBlockConstant(parameters); problem.SetParameterBlockVariable(parameters); ``` --- ### 12. Error Handling **C++:** ```cpp // Ceres throws exceptions or returns error codes try { problem.AddParameterBlock(parameters, size); } catch (std::exception& e) { std::cerr << e.what() << std::endl; } ``` **C#:** ```csharp try { problem.AddParameterBlock(parameters, size); } catch (CeresException ex) { Console.WriteLine($"Error: {ex.Message} (Code: {ex.ErrorCode})"); } ``` **Key Differences:** - All errors are wrapped in `CeresException` - Error codes are available via `ex.ErrorCode` property - Error messages are human-readable --- ## πŸ“š Complete Conversion Examples ### Example 1: Simple Linear Least Squares **C++:** ```cpp #include int main() { ceres::Problem problem; double x = 0.5; problem.AddParameterBlock(&x, 1); auto* cost_function = new ceres::AutoDiffCostFunction( new CostFunctor); problem.AddResidualBlock(cost_function, nullptr, &x); ceres::Solver::Options options; options.linear_solver_type = ceres::DENSE_QR; ceres::Solver::Summary summary; ceres::Solve(options, &problem, &summary); std::cout << summary.BriefReport() << std::endl; return 0; } ``` **C#:** ```csharp using CeresSharp; using CeresSharp.Enums; var problem = new Problem(); var x = new double[] { 0.5 }; problem.AddParameterBlock(x, x.Length); var costFunction = new AutoDiffCostFunction( (parameters, residuals) => { residuals[0] = parameters[0][0] - 1.0; // Your cost function return true; }, numResiduals: 1, parameterBlockSizes: new[] { 1 }); problem.AddResidualBlock(costFunction, lossFunction: null, parameterBlocks: new[] { x }); // ⚠️ NOTE: Problem now owns costFunction - don't dispose it manually var options = new SolverOptions { LinearSolverType = LinearSolverType.DenseQr }; var summary = problem.Solve(options); Console.WriteLine(summary.FullReport); ``` --- ### Example 2: Pose Graph Optimization (with Quaternion) **C++:** ```cpp ceres::Problem problem; double pose[7] = {x, y, z, qx, qy, qz, qw}; problem.AddParameterBlock(pose, 7); problem.SetManifold(pose, new ceres::QuaternionManifold); auto* cost_function = new ceres::AutoDiffCostFunction( new PoseCostFunctor); problem.AddResidualBlock(cost_function, new ceres::HuberLoss(1.0), pose); ``` **C#:** ```csharp var problem = new Problem(); var pose = new double[] { x, y, z, qx, qy, qz, qw }; problem.AddParameterBlock(pose, pose.Length); using var quaternionManifold = new QuaternionManifold(); problem.SetManifold(pose, quaternionManifold); var costFunction = new AutoDiffCostFunction( (parameters, residuals) => { // Your pose cost function // parameters[0] is pose[7] // residuals[0..5] are 6 residuals return true; }, numResiduals: 6, parameterBlockSizes: new[] { 7 }); using var loss = new HuberLoss(1.0); problem.AddResidualBlock(costFunction, loss, parameterBlocks: new[] { pose }); ``` --- ### Example 3: AutoDiffManifold (Custom Manifolds) **C++:** ```cpp // AutoDiffManifold for custom manifolds (Ceres 2.2.0) // Example: Euclidean manifold struct EuclideanManifold { template bool Plus(const T* x, const T* delta, T* x_plus_delta) const { for (int i = 0; i < 3; i++) { x_plus_delta[i] = x[i] + delta[i]; } return true; } template bool Minus(const T* y, const T* x, T* y_minus_x) const { for (int i = 0; i < 3; i++) { y_minus_x[i] = y[i] - x[i]; } return true; } }; auto* manifold = new ceres::AutoDiffManifold( new EuclideanManifold); problem.SetManifold(parameters, manifold); ``` **C#:** ```csharp // AutoDiffManifold with callbacks (similar pattern to AutoDiffCostFunction) using var manifold = new AutoDiffManifold( ambientSize: 3, tangentSize: 3, plus: (x, delta, xPlusDelta) => { // Plus operation: x + delta β†’ x_plus_delta for (int i = 0; i < 3; i++) xPlusDelta[i] = x[i] + delta[i]; return true; }, minus: (y, x, yMinusX) => { // Minus operation: y - x β†’ y_minus_x for (int i = 0; i < 3; i++) yMinusX[i] = y[i] - x[i]; return true; }); problem.SetManifold(parameters, manifold); // ⚠️ IMPORTANT: Problem now owns the manifold ``` **Use Case: ConstantYawQuaternion (Cartographer)** ```csharp // Constant yaw quaternion manifold (4D ambient, 3D tangent) // Only roll and pitch vary, yaw is constant using var constantYawManifold = new AutoDiffManifold( ambientSize: 4, // Quaternion (qx, qy, qz, qw) tangentSize: 3, // Only roll, pitch vary (yaw fixed) plus: (x, delta, xPlusDelta) => { // Implement Plus with constant yaw constraint // x is quaternion, delta is [roll_delta, pitch_delta, yaw_delta=0] // xPlusDelta is resulting quaternion // ... implementation ... return true; }, minus: (y, x, yMinusX) => { // Implement Minus with constant yaw constraint // y, x are quaternions // yMinusX is [roll_diff, pitch_diff, yaw_diff=0] // ... implementation ... return true; }); ``` **Key Differences:** - C++ uses template class `AutoDiffManifold` - C# uses callback-based API with delegates (similar to `AutoDiffCostFunction`) - Jacobians are computed automatically via numeric differentiation (handled by C wrapper) - Perfect for custom geometric constraints (ConstantYawQuaternion, domain-specific manifolds) --- ### Example 4: Using Interpolators **C++:** ```cpp double data[10] = { /* ... */ }; ceres::BiCubicInterpolator interpolator(data, 5, 2); double value, gradient_x, gradient_y; interpolator.Evaluate(1.5, 0.5, &value, &gradient_x, &gradient_y); ``` **C#:** ```csharp var data = new double[10] { /* ... */ }; using var interpolator = new BiCubicInterpolator(data, rows: 5, cols: 2); interpolator.Evaluate(x: 1.5, y: 0.5, out double value, out double? gradientX, out double? gradientY); ``` --- ## ⚠️ Important Differences & Gotchas ### 1. Memory Management **C++:** - Manual memory management with `new`/`delete` - Objects can be stack-allocated **C#:** - All objects are heap-allocated - Use `using` statements for automatic disposal - Objects implement `IDisposable` - **always dispose them** ```csharp // βœ… Correct using var problem = new Problem(); using var options = new SolverOptions(); // Auto-disposed at end of scope // ❌ Wrong - Memory leak var problem = new Problem(); // Never disposed! ``` ### 2. Array Handling **C++:** - Raw pointers: `double* parameters` - Stack arrays: `double parameters[10]` **C#:** - Managed arrays: `double[] parameters` - Arrays are automatically pinned when passed to native code - **Do not modify arrays during solve** (they're pinned) ### 3. Cost Function Callbacks **C++:** - Functors with `operator()` - Template-based **C#:** - Lambda expressions or delegates - Must return `bool` (true = success, false = failure) - Parameters: `double[][]` (array of parameter blocks) - Residuals: `double[]` (output array) ### 4. Null Pointers **C++:** - `nullptr` for optional parameters **C#:** - `null` for optional parameters (e.g., `lossFunction: null`) ### 5. Enums **C++:** - Scoped enums: `ceres::DENSE_QR` - Integer values **C#:** - Strongly-typed enums: `LinearSolverType.DenseQr` - Type-safe, no integer casting needed --- ## πŸ” Common Conversion Patterns ### Pattern 1: Multiple Residual Blocks **C++:** ```cpp for (int i = 0; i < num_observations; ++i) { problem.AddResidualBlock(cost_function, nullptr, ¶meters[i]); } ``` **C#:** ```csharp for (int i = 0; i < numObservations; i++) { problem.AddResidualBlock(costFunction, lossFunction: null, parameterBlocks: new[] { parameters[i] }); } ``` ### Pattern 2: Dynamic Cost Functions **C++:** ```cpp auto* cost_function = new ceres::DynamicAutoDiffCostFunction( new CostFunctor); cost_function->AddParameterBlock(2); cost_function->SetNumResiduals(1); ``` **C#:** ```csharp var costFunction = new DynamicAutoDiffCostFunction( (parameters, residuals) => { /* ... */ }, numResiduals: 1, parameterBlockSizes: new[] { 2 }); ``` ### Pattern 3: Numeric Differentiation **C++:** ```cpp auto* cost_function = new ceres::NumericDiffCostFunction(new CostFunctor); ``` **C#:** ```csharp var costFunction = new NumericDiffCostFunction( (parameters, residuals) => { /* ... */ }, method: NumericDiffMethod.Central, numResiduals: 1, parameterBlockSizes: new[] { 1 }); ``` --- ## πŸ“– API Reference Quick Lookup ### Problem Operations | C++ | C# | |-----|-----| | `problem.AddParameterBlock(ptr, size)` | `problem.AddParameterBlock(array, size)` | | `problem.SetParameterBlockConstant(ptr)` | `problem.SetParameterBlockConstant(array)` | | `problem.AddResidualBlock(cf, loss, ptrs)` | `problem.AddResidualBlock(cf, loss, arrays)` | | `problem.NumParameterBlocks()` | `problem.NumParameterBlocks` (property) | | `problem.NumResidualBlocks()` | `problem.NumResidualBlocks` (property) | ### SolverOptions Properties | C++ | C# | |-----|-----| | `options.linear_solver_type` | `options.LinearSolverType` | | `options.max_num_iterations` | `options.MaxNumIterations` | | `options.function_tolerance` | `options.FunctionTolerance` | | `options.num_threads` | `options.NumThreads` | | `options.minimizer_progress_to_stdout` | `options.MinimizerProgressToStdout` | ### SolverSummary Properties | C++ | C# | |-----|-----| | `summary.termination_type` | `summary.TerminationType` | | `summary.final_cost` | `summary.FinalCost` | | `summary.iterations` | `summary.Iterations` | | `summary.BriefReport()` | `summary.FullReport` (property) | --- ## πŸŽ“ Best Practices for AI Agents ### 1. Always Use `using` Statements ```csharp // βœ… Correct using var problem = new Problem(); using var options = new SolverOptions(); // Auto-disposed // ❌ Wrong var problem = new Problem(); var options = new SolverOptions(); // Must manually dispose: problem.Dispose(); options.Dispose(); ``` ### 2. Handle Exceptions ```csharp try { problem.AddParameterBlock(parameters, size); var summary = problem.Solve(options); if (summary.TerminationType == TerminationType.Convergence) { // Success } } catch (CeresException ex) { // Handle Ceres-specific errors Console.WriteLine($"Ceres error: {ex.Message}"); } catch (ArgumentException ex) { // Handle invalid arguments Console.WriteLine($"Invalid argument: {ex.Message}"); } ``` ### 3. Reuse Objects When Possible ```csharp // βœ… Good - Reuse options var options = new SolverOptions { MaxNumIterations = 100 }; for (int i = 0; i < 10; i++) { var problem = new Problem(); // ... setup problem ... var summary = problem.Solve(options); // Reuse options problem.Dispose(); } options.Dispose(); ``` ### 4. Use Constants for Default Values ```csharp var options = new SolverOptions { MaxNumIterations = Constants.DefaultMaxNumIterations, FunctionTolerance = Constants.DefaultFunctionTolerance, GradientTolerance = Constants.DefaultGradientTolerance, ParameterTolerance = Constants.DefaultParameterTolerance }; ``` ### 5. Check Solver Results ```csharp var summary = problem.Solve(options); switch (summary.TerminationType) { case TerminationType.Convergence: // Success break; case TerminationType.NoConvergence: // Did not converge break; case TerminationType.Failure: // Solver failure break; case TerminationType.UserSuccess: // User stopped via callback break; } ``` --- ## πŸ”§ Advanced Features ### Covariance Estimation **C++:** ```cpp ceres::Covariance::Options options; ceres::Covariance covariance(options); covariance.Compute(covariance_blocks, &problem); ``` **C#:** ```csharp using var covOptions = new CovarianceOptions(); using var covariance = new Covariance(covOptions); covariance.Compute(problem, covOptions, parameterBlocks); ``` ### Gradient Checker **C++:** ```cpp ceres::GradientChecker checker(cost_function, manifolds, options); checker.Probe(parameters, relative_precision, &error_message); ``` **C#:** ```csharp using var checkerOptions = new GradientCheckerOptions(); using var checker = new GradientChecker(costFunction, manifolds, checkerOptions); var success = checker.Probe(parameters, relativePrecision, out string? errorMessage); ``` ### Context for Performance **C++:** ```cpp ceres::Context context; ceres::Problem::Options problem_options; problem_options.context = &context; ceres::Problem problem(problem_options); ``` **C#:** ```csharp using var context = new Context(); using var problemOptions = new ProblemOptions(); problemOptions.SetContext(context); var problem = new Problem(problemOptions); ``` --- ## πŸ“ Conversion Checklist When converting C++ Ceres code to C#, check: - [ ] All `ceres::` namespaces β†’ `CeresSharp.` namespaces - [ ] Raw pointers β†’ Managed arrays (`double[]`) - [ ] Stack objects β†’ Heap objects with `using` - [ ] Functors β†’ Lambda expressions - [ ] `nullptr` β†’ `null` - [ ] Snake_case β†’ PascalCase (properties) - [ ] Enum values β†’ Strongly-typed enums - [ ] Exception handling β†’ `CeresException` - [ ] Memory management β†’ `using` statements - [ ] Callbacks β†’ Delegates with proper pinning - [ ] `AutoDiffLocalParameterization` β†’ `AutoDiffManifold` (Ceres 2.2.0 migration) - [ ] **CRITICAL**: Don't manually dispose cost/loss functions added to Problem - [ ] **CRITICAL**: Let Problem manage ownership of added objects --- ## πŸš€ Status **Current Status**: βœ… **100% Complete** - βœ… All 220+ APIs implemented (including AutoDiffManifold) - βœ… All core functionality working - βœ… Memory management complete - βœ… Error handling complete - βœ… Callbacks with proper memory management - βœ… AutoDiffManifold implemented ⭐ **NEW** - Ready for Cartographer integration - βœ… Ready for production use --- ## πŸ“š Additional Resources - **Implementation Progress**: See `IMPLEMENTATION_PROGRESS.md` for detailed status - **API Documentation**: All classes have XML documentation comments - **Examples**: See conversion examples above --- ## ⚑ Quick Conversion Template ```csharp // 1. Add using statements using CeresSharp; using CeresSharp.Enums; // 2. Create problem using var problem = new Problem(); // 3. Add parameters var parameters = new double[] { /* values */ }; problem.AddParameterBlock(parameters, parameters.Length); // 4. Create cost function var costFunction = new AutoDiffCostFunction( (params, residuals) => { /* implementation */ return true; }, numResiduals: N, parameterBlockSizes: new[] { /* sizes */ }); // 5. Add residual block using var loss = new HuberLoss(1.0); // or null problem.AddResidualBlock(costFunction, loss, new[] { parameters }); // ⚠️ NOTE: Problem now owns costFunction and loss // Don't manually dispose them - Problem will cleanup automatically // 6. Configure solver using var options = new SolverOptions { LinearSolverType = LinearSolverType.DenseQr, MaxNumIterations = 100 }; // 7. Solve using var summary = problem.Solve(options); // 8. Check results if (summary.TerminationType == TerminationType.Convergence) { // Success - use optimized parameters } ``` --- ## ⚠️ Critical: Memory Management and Ownership ### Cost Function Ownership **IMPORTANT**: When you add a cost function to a Problem via `AddResidualBlock()`, the **Problem takes ownership** of the cost function. This means: 1. βœ… **DO**: Let Problem manage the cost function lifecycle 2. ❌ **DON'T**: Manually dispose cost functions that have been added to a Problem 3. βœ… **DO**: Dispose the Problem, which will automatically cleanup all owned cost functions **Correct Usage**: ```csharp using var problem = new Problem(); var x = new double[] { 0.0 }; problem.AddParameterBlock(x, x.Length); // Create cost function var costFunction = new AutoDiffCostFunction( (parameters, residuals) => { residuals[0] = parameters[0][0] - 2.0; return true; }, numResiduals: 1, parameterBlockSizes: new[] { 1 }); // Add to problem - Problem now owns the cost function problem.AddResidualBlock(costFunction, lossFunction: null, parameterBlocks: new[] { x }); // βœ… CORRECT: Just dispose the problem // Cost function will be automatically cleaned up by Problem // ❌ WRONG: costFunction.Dispose(); // Don't do this! using var options = new SolverOptions { /* ... */ }; using var summary = problem.Solve(options); // Problem.Dispose() is called automatically by 'using' statement ``` **Why This Matters**: - If you manually dispose a cost function that was added to a Problem, you'll get a **double-free crash** in the finalizer thread - The Problem will try to delete the cost function when it's disposed, but it's already been deleted - This causes a segmentation fault in the native library ### Loss Function Ownership Similar to cost functions, **Problem also owns loss functions** when added via `AddResidualBlock()`: ```csharp using var loss = new HuberLoss(1.0); problem.AddResidualBlock(costFunction, loss, parameterBlocks); // βœ… CORRECT: Let Problem cleanup the loss function // ❌ WRONG: loss.Dispose(); // Don't do this! ``` ### Best Practices for Memory Management 1. **Always use `using` statements** for Problem, SolverOptions, and SolverSummary: ```csharp using var problem = new Problem(); using var options = new SolverOptions { /* ... */ }; using var summary = problem.Solve(options); ``` 2. **Don't manually dispose objects owned by Problem**: - Cost functions added via `AddResidualBlock()` - Loss functions added via `AddResidualBlock()` - Manifolds set via `SetManifold()` 3. **Parameter blocks are managed automatically**: - Arrays are pinned automatically when passed to native code - They remain valid for the lifetime of the Problem - No manual memory management needed 4. **Dispose order matters** (handled automatically by `using`): ```csharp // Correct order (automatic with 'using'): // 1. Dispose SolverSummary (innermost) // 2. Dispose SolverOptions // 3. Dispose Problem (outermost) - this cleans up all owned objects ``` ### Known Issues and Workarounds #### Double-Free Prevention The library has been fixed to prevent double-free crashes. The fix ensures that: - Cost functions are not deleted by `CostFunctionHandle` when they're owned by Problem - Problem properly cleans up all owned objects when disposed - Finalizers don't attempt to free already-freed resources **If you encounter crashes**: 1. Ensure you're not manually disposing cost/loss functions that were added to a Problem 2. Use `using` statements for proper disposal order 3. Don't keep references to disposed objects --- ## πŸ” Troubleshooting ### Common Issues #### Issue: "Test Run Aborted" or Process Crash **Cause**: Double-free of cost functions or improper disposal order **Solution**: - βœ… Use `using` statements for automatic disposal - βœ… Don't manually dispose cost/loss functions added to Problem - βœ… Let Problem manage the lifecycle of owned objects #### Issue: "Initial residual and Jacobian evaluation failed" **Cause**: Invalid cost function implementation or parameter setup **Solution**: - Check that your cost function returns `true` on success - Verify parameter block sizes match your cost function - Ensure initial parameter values are valid #### Issue: Tests pass but process crashes after completion **Cause**: This was a known issue (now fixed) related to double-free in finalizer thread **Solution**: - βœ… Fixed in current version - Ensure you're using the latest version of CeresSharp - Follow the memory management best practices above --- **Last Updated**: 2024-12-19 **Version**: 1.0.0 **Ceres Solver Version**: 2.2.0 **AutoDiffManifold**: βœ… **Implemented** - Ready for Cartographer integration (ConstantYawQuaternion use case)