/* Ceres Wrapper - Extended C API Implementation * Copyright 2024 RobotNet10. All rights reserved. * * This implementation provides additional C APIs that are missing from the * official Ceres C API but are required for Cartographer integration. */ #include "ceres_wrapper.h" #include #include #include #include #include #include #include #include #include #include "ceres/ceres.h" #include "ceres/cubic_interpolation.h" #include "ceres/manifold.h" #include "ceres/problem.h" #include "ceres/solver.h" #include "ceres/sphere_manifold.h" #include "ceres/line_manifold.h" #include "ceres/dynamic_autodiff_cost_function.h" #include "ceres/product_manifold.h" #include "ceres/iteration_callback.h" #include "ceres/numeric_diff_cost_function.h" #include "ceres/dynamic_numeric_diff_cost_function.h" #include "ceres/covariance.h" #include "ceres/gradient_checker.h" #include "ceres/context.h" #include "ceres/loss_function.h" #include "ceres/autodiff_manifold.h" #include "ceres/version.h" #include #include #include // ============================================================================ // Error Code Utilities // ============================================================================ const char* ceres_wrapper_get_error_message(ceres_wrapper_error_code_t error_code) { switch (error_code) { case CERES_WRAPPER_SUCCESS: return "Success"; case CERES_WRAPPER_ERROR_NULL_POINTER: return "Null pointer argument"; case CERES_WRAPPER_ERROR_INVALID_PARAMETER: return "Invalid parameter value"; case CERES_WRAPPER_ERROR_INVALID_ENUM: return "Invalid enum value"; case CERES_WRAPPER_ERROR_EXCEPTION: return "C++ exception occurred"; case CERES_WRAPPER_ERROR_OUT_OF_MEMORY: return "Memory allocation failed"; case CERES_WRAPPER_ERROR_INVALID_OPERATION: return "Invalid operation for current state"; case CERES_WRAPPER_ERROR_BUFFER_TOO_SMALL: return "Output buffer too small"; case CERES_WRAPPER_ERROR_NOT_FOUND: return "Resource not found"; case CERES_WRAPPER_ERROR_ALREADY_EXISTS: return "Resource already exists"; default: return "Unknown error"; } } // ============================================================================ // Library Information and Version // ============================================================================ const char* ceres_wrapper_get_version_string() { return CERES_VERSION_STRING; } int ceres_wrapper_get_version_major() { return CERES_VERSION_MAJOR; } int ceres_wrapper_get_version_minor() { return CERES_VERSION_MINOR; } int ceres_wrapper_get_version_revision() { return CERES_VERSION_REVISION; } ceres_wrapper_error_code_t ceres_wrapper_get_detailed_version_string( char* buffer, int buffer_size) { if (!buffer || buffer_size <= 0) { return CERES_WRAPPER_ERROR_NULL_POINTER; } try { std::ostringstream oss; oss << CERES_VERSION_STRING; // Add Eigen version #if defined(EIGEN_WORLD_VERSION) && defined(EIGEN_MAJOR_VERSION) && defined(EIGEN_MINOR_VERSION) oss << "-eigen-(" << EIGEN_WORLD_VERSION << "." << EIGEN_MAJOR_VERSION << "." << EIGEN_MINOR_VERSION << ")"; #else oss << "-eigen-(unknown)"; #endif // Add LAPACK info #ifdef CERES_NO_LAPACK oss << "-no_lapack"; #else oss << "-lapack"; #endif // Add SuiteSparse info #ifndef CERES_NO_SUITESPARSE #ifdef CERES_SUITESPARSE_VERSION oss << "-suitesparse-(" << CERES_SUITESPARSE_VERSION << ")"; #endif #endif // Add METIS info #if !defined(CERES_NO_EIGEN_METIS) || !defined(CERES_NO_CHOLMOD_PARTITION) #ifdef CERES_METIS_VERSION oss << "-metis-(" << CERES_METIS_VERSION << ")"; #endif #endif // Add AccelerateSparse info #ifndef CERES_NO_ACCELERATE_SPARSE oss << "-acceleratesparse"; #endif // Add EigenSparse info #ifdef CERES_USE_EIGEN_SPARSE oss << "-eigensparse"; #endif // Add Schur specializations info #ifdef CERES_RESTRUCT_SCHUR_SPECIALIZATIONS oss << "-no_schur_specializations"; #endif // Add custom BLAS info #ifdef CERES_NO_CUSTOM_BLAS oss << "-no_custom_blas"; #endif // Add CUDA info #ifndef CERES_NO_CUDA #ifdef CUDART_VERSION oss << "-cuda-(" << CUDART_VERSION << ")"; #endif #endif std::string version_str = oss.str(); int copy_size = std::min(static_cast(version_str.size()), buffer_size - 1); std::strncpy(buffer, version_str.c_str(), copy_size); buffer[copy_size] = '\0'; return CERES_WRAPPER_SUCCESS; } catch (...) { return CERES_WRAPPER_ERROR_EXCEPTION; } } ceres_wrapper_error_code_t ceres_wrapper_get_library_info( char* buffer, int buffer_size) { if (!buffer || buffer_size <= 0) { return CERES_WRAPPER_ERROR_NULL_POINTER; } try { std::ostringstream oss; oss << "Ceres Solver Library Information\n"; oss << "================================\n\n"; // Version information oss << "Version: " << CERES_VERSION_STRING << "\n"; oss << " Major: " << CERES_VERSION_MAJOR << "\n"; oss << " Minor: " << CERES_VERSION_MINOR << "\n"; oss << " Revision: " << CERES_VERSION_REVISION << "\n\n"; // Build configuration oss << "Build Configuration:\n"; // Eigen version #if defined(EIGEN_WORLD_VERSION) && defined(EIGEN_MAJOR_VERSION) && defined(EIGEN_MINOR_VERSION) oss << " Eigen: " << EIGEN_WORLD_VERSION << "." << EIGEN_MAJOR_VERSION << "." << EIGEN_MINOR_VERSION << "\n"; #else oss << " Eigen: Unknown\n"; #endif // LAPACK #ifdef CERES_NO_LAPACK oss << " LAPACK: Disabled\n"; #else oss << " LAPACK: Enabled\n"; #endif // SuiteSparse #ifdef CERES_NO_SUITESPARSE oss << " SuiteSparse: Disabled\n"; #else oss << " SuiteSparse: Enabled"; #ifdef CERES_SUITESPARSE_VERSION oss << " (version " << CERES_SUITESPARSE_VERSION << ")"; #endif oss << "\n"; #endif // METIS #if defined(CERES_NO_EIGEN_METIS) && defined(CERES_NO_CHOLMOD_PARTITION) oss << " METIS: Disabled\n"; #else oss << " METIS: Enabled"; #ifdef CERES_METIS_VERSION oss << " (version " << CERES_METIS_VERSION << ")"; #endif oss << "\n"; #endif // AccelerateSparse #ifdef CERES_NO_ACCELERATE_SPARSE oss << " AccelerateSparse: Disabled\n"; #else oss << " AccelerateSparse: Enabled\n"; #endif // EigenSparse #ifdef CERES_USE_EIGEN_SPARSE oss << " EigenSparse: Enabled\n"; #else oss << " EigenSparse: Disabled\n"; #endif // Schur specializations #ifdef CERES_RESTRUCT_SCHUR_SPECIALIZATIONS oss << " Schur Specializations: Disabled\n"; #else oss << " Schur Specializations: Enabled\n"; #endif // Custom BLAS #ifdef CERES_NO_CUSTOM_BLAS oss << " Custom BLAS: Disabled\n"; #else oss << " Custom BLAS: Enabled\n"; #endif // CUDA #ifdef CERES_NO_CUDA oss << " CUDA: Disabled\n"; #else oss << " CUDA: Enabled"; #ifdef CUDART_VERSION oss << " (version " << CUDART_VERSION << ")"; #endif oss << "\n"; #endif std::string info_str = oss.str(); int copy_size = std::min(static_cast(info_str.size()), buffer_size - 1); std::strncpy(buffer, info_str.c_str(), copy_size); buffer[copy_size] = '\0'; return CERES_WRAPPER_SUCCESS; } catch (...) { return CERES_WRAPPER_ERROR_EXCEPTION; } } // ============================================================================ // Forward declarations - Define structs early for use in classes // ============================================================================ // Define ceres_solver_summary_s early so it can be used in IterationCallbackWrapper struct ceres_solver_summary_s { ceres::Solver::Summary summary; }; typedef struct ceres_solver_summary_s ceres_solver_summary_t; // Wrapper to bridge C callback to Ceres IterationCallback // Defined here so it can be used in ceres_solver_options_s class IterationCallbackWrapper : public ceres::IterationCallback { public: IterationCallbackWrapper(ceres_iteration_callback_t callback, void* user_data) : callback_(callback), user_data_(user_data) {} ceres::CallbackReturnType operator()(const ceres::IterationSummary& summary) override { // Validate callback pointer (user_data_ may legitimately be NULL) if (!callback_) { return ceres::SOLVER_TERMINATE_SUCCESSFULLY; } // Create a temporary solver summary wrapper for the callback // Note: This is a simplified version - we only pass basic info ceres_solver_summary_t temp_summary; // IterationSummary doesn't have termination_type, use default temp_summary.summary.termination_type = ceres::TerminationType::NO_CONVERGENCE; temp_summary.summary.initial_cost = summary.cost; temp_summary.summary.final_cost = summary.cost; temp_summary.summary.iterations.clear(); temp_summary.summary.num_successful_steps = summary.step_is_successful ? 1 : 0; temp_summary.summary.num_unsuccessful_steps = summary.step_is_successful ? 0 : 1; int result = callback_(user_data_, &temp_summary); // Convert return value: 0 = continue, non-zero = stop return (result == 0) ? ceres::SOLVER_CONTINUE : ceres::SOLVER_TERMINATE_SUCCESSFULLY; } private: ceres_iteration_callback_t callback_; void* user_data_; }; // ============================================================================ // Solver Options // ============================================================================ struct ceres_solver_options_s { ceres::Solver::Options options; std::unique_ptr callback_wrapper; // Store callback wrapper }; ceres_solver_options_t* ceres_wrapper_create_solver_options() { return new ceres_solver_options_t; } void ceres_wrapper_free_solver_options(ceres_solver_options_t* options) { delete options; } void ceres_wrapper_solver_options_set_linear_solver_type( ceres_solver_options_t* options, int linear_solver_type) { if (!options) return; options->options.linear_solver_type = static_cast(linear_solver_type); } int ceres_wrapper_solver_options_get_linear_solver_type( const ceres_solver_options_t* options) { if (!options) return 0; return static_cast(options->options.linear_solver_type); } void ceres_wrapper_solver_options_set_minimizer_type( ceres_solver_options_t* options, int minimizer_type) { if (!options) return; options->options.minimizer_type = static_cast(minimizer_type); } int ceres_wrapper_solver_options_get_minimizer_type( const ceres_solver_options_t* options) { if (!options) return 0; return static_cast(options->options.minimizer_type); } void ceres_wrapper_solver_options_set_max_num_iterations( ceres_solver_options_t* options, int max_num_iterations) { if (!options) return; options->options.max_num_iterations = max_num_iterations; } int ceres_wrapper_solver_options_get_max_num_iterations( const ceres_solver_options_t* options) { if (!options) return 0; return options->options.max_num_iterations; } void ceres_wrapper_solver_options_set_num_threads( ceres_solver_options_t* options, int num_threads) { if (!options) return; options->options.num_threads = num_threads; } int ceres_wrapper_solver_options_get_num_threads( const ceres_solver_options_t* options) { if (!options) return 0; return options->options.num_threads; } void ceres_wrapper_solver_options_set_function_tolerance( ceres_solver_options_t* options, double function_tolerance) { if (!options) return; options->options.function_tolerance = function_tolerance; } double ceres_wrapper_solver_options_get_function_tolerance( const ceres_solver_options_t* options) { if (!options) return 0.0; return options->options.function_tolerance; } void ceres_wrapper_solver_options_set_gradient_tolerance( ceres_solver_options_t* options, double gradient_tolerance) { if (!options) return; options->options.gradient_tolerance = gradient_tolerance; } double ceres_wrapper_solver_options_get_gradient_tolerance( const ceres_solver_options_t* options) { if (!options) return 0.0; return options->options.gradient_tolerance; } void ceres_wrapper_solver_options_set_parameter_tolerance( ceres_solver_options_t* options, double parameter_tolerance) { if (!options) return; options->options.parameter_tolerance = parameter_tolerance; } double ceres_wrapper_solver_options_get_parameter_tolerance( const ceres_solver_options_t* options) { if (!options) return 0.0; return options->options.parameter_tolerance; } void ceres_wrapper_solver_options_set_initial_trust_region_radius( ceres_solver_options_t* options, double radius) { if (!options) return; options->options.initial_trust_region_radius = radius; } double ceres_wrapper_solver_options_get_initial_trust_region_radius( const ceres_solver_options_t* options) { if (!options) return 0.0; return options->options.initial_trust_region_radius; } void ceres_wrapper_solver_options_set_max_trust_region_radius( ceres_solver_options_t* options, double radius) { if (!options) return; options->options.max_trust_region_radius = radius; } double ceres_wrapper_solver_options_get_max_trust_region_radius( const ceres_solver_options_t* options) { if (!options) return 0.0; return options->options.max_trust_region_radius; } void ceres_wrapper_solver_options_set_min_trust_region_radius( ceres_solver_options_t* options, double radius) { if (!options) return; options->options.min_trust_region_radius = radius; } double ceres_wrapper_solver_options_get_min_trust_region_radius( const ceres_solver_options_t* options) { if (!options) return 0.0; return options->options.min_trust_region_radius; } void ceres_wrapper_solver_options_set_preconditioner_type( ceres_solver_options_t* options, int preconditioner_type) { if (!options) return; options->options.preconditioner_type = static_cast(preconditioner_type); } int ceres_wrapper_solver_options_get_preconditioner_type( const ceres_solver_options_t* options) { if (!options) return 0; return static_cast(options->options.preconditioner_type); } void ceres_wrapper_solver_options_set_trust_region_strategy_type( ceres_solver_options_t* options, int trust_region_strategy_type) { if (!options) return; options->options.trust_region_strategy_type = static_cast(trust_region_strategy_type); } int ceres_wrapper_solver_options_get_trust_region_strategy_type( const ceres_solver_options_t* options) { if (!options) return 0; return static_cast(options->options.trust_region_strategy_type); } void ceres_wrapper_solver_options_set_dogleg_type( ceres_solver_options_t* options, int dogleg_type) { if (!options) return; options->options.dogleg_type = static_cast(dogleg_type); } int ceres_wrapper_solver_options_get_dogleg_type( const ceres_solver_options_t* options) { if (!options) return 0; return static_cast(options->options.dogleg_type); } void ceres_wrapper_solver_options_set_use_nonmonotonic_steps( ceres_solver_options_t* options, int use_nonmonotonic_steps) { if (!options) return; options->options.use_nonmonotonic_steps = (use_nonmonotonic_steps != 0); } int ceres_wrapper_solver_options_get_use_nonmonotonic_steps( const ceres_solver_options_t* options) { if (!options) return 0; return options->options.use_nonmonotonic_steps ? 1 : 0; } void ceres_wrapper_solver_options_set_max_consecutive_nonmonotonic_steps( ceres_solver_options_t* options, int max_steps) { if (!options) return; options->options.max_consecutive_nonmonotonic_steps = max_steps; } int ceres_wrapper_solver_options_get_max_consecutive_nonmonotonic_steps( const ceres_solver_options_t* options) { if (!options) return 0; return options->options.max_consecutive_nonmonotonic_steps; } void ceres_wrapper_solver_options_set_max_num_consecutive_invalid_steps( ceres_solver_options_t* options, int max_steps) { if (!options) return; options->options.max_num_consecutive_invalid_steps = max_steps; } int ceres_wrapper_solver_options_get_max_num_consecutive_invalid_steps( const ceres_solver_options_t* options) { if (!options) return 0; return options->options.max_num_consecutive_invalid_steps; } void ceres_wrapper_solver_options_set_min_relative_decrease( ceres_solver_options_t* options, double min_relative_decrease) { if (!options) return; options->options.min_relative_decrease = min_relative_decrease; } double ceres_wrapper_solver_options_get_min_relative_decrease( const ceres_solver_options_t* options) { if (!options) return 0.0; return options->options.min_relative_decrease; } void ceres_wrapper_solver_options_set_logging_type( ceres_solver_options_t* options, int logging_type) { if (!options) return; options->options.logging_type = static_cast(logging_type); } int ceres_wrapper_solver_options_get_logging_type( const ceres_solver_options_t* options) { if (!options) return 0; return static_cast(options->options.logging_type); } void ceres_wrapper_solver_options_set_minimizer_progress_to_stdout( ceres_solver_options_t* options, int minimizer_progress_to_stdout) { if (!options) return; options->options.minimizer_progress_to_stdout = (minimizer_progress_to_stdout != 0); } int ceres_wrapper_solver_options_get_minimizer_progress_to_stdout( const ceres_solver_options_t* options) { if (!options) return 0; return options->options.minimizer_progress_to_stdout ? 1 : 0; } int ceres_wrapper_solver_options_is_valid( const ceres_solver_options_t* options, char* error_message, int error_message_size) { if (!options) return 0; std::string error; bool valid = options->options.IsValid(&error); if (!valid && error_message && error_message_size > 0) { int copy_size = std::min(static_cast(error.size()), error_message_size - 1); std::strncpy(error_message, error.c_str(), copy_size); error_message[copy_size] = '\0'; } return valid ? 1 : 0; } // ============================================================================ // Solver Summary // ============================================================================ // Note: ceres_solver_summary_s is defined earlier in the file ceres_solver_summary_t* ceres_wrapper_create_solver_summary() { return new ceres_solver_summary_t; } void ceres_wrapper_free_solver_summary(ceres_solver_summary_t* summary) { delete summary; } int ceres_wrapper_solver_summary_get_termination_type( const ceres_solver_summary_t* summary) { if (!summary) return 0; return static_cast(summary->summary.termination_type); } void ceres_wrapper_solver_summary_get_message( const ceres_solver_summary_t* summary, char* message, int message_size) { if (!summary) return; if (message && message_size > 0) { const std::string& msg = summary->summary.message; int copy_size = std::min(static_cast(msg.size()), message_size - 1); std::strncpy(message, msg.c_str(), copy_size); message[copy_size] = '\0'; } } double ceres_wrapper_solver_summary_get_initial_cost( const ceres_solver_summary_t* summary) { if (!summary) return 0.0; return summary->summary.initial_cost; } double ceres_wrapper_solver_summary_get_final_cost( const ceres_solver_summary_t* summary) { if (!summary) return 0.0; return summary->summary.final_cost; } int ceres_wrapper_solver_summary_get_iterations( const ceres_solver_summary_t* summary) { if (!summary) return 0; return summary->summary.iterations.size(); } int ceres_wrapper_solver_summary_get_num_successful_steps( const ceres_solver_summary_t* summary) { if (!summary) return 0; return summary->summary.num_successful_steps; } int ceres_wrapper_solver_summary_get_num_unsuccessful_steps( const ceres_solver_summary_t* summary) { if (!summary) return 0; return summary->summary.num_unsuccessful_steps; } int ceres_wrapper_solver_summary_get_num_inner_iteration_steps( const ceres_solver_summary_t* summary) { if (!summary) return 0; return summary->summary.num_inner_iteration_steps; } double ceres_wrapper_solver_summary_get_total_time_in_seconds( const ceres_solver_summary_t* summary) { if (!summary) return 0.0; return summary->summary.total_time_in_seconds; } double ceres_wrapper_solver_summary_get_preprocessor_time_in_seconds( const ceres_solver_summary_t* summary) { if (!summary) return 0.0; return summary->summary.preprocessor_time_in_seconds; } double ceres_wrapper_solver_summary_get_minimizer_time_in_seconds( const ceres_solver_summary_t* summary) { if (!summary) return 0.0; return summary->summary.minimizer_time_in_seconds; } double ceres_wrapper_solver_summary_get_postprocessor_time_in_seconds( const ceres_solver_summary_t* summary) { if (!summary) return 0.0; return summary->summary.postprocessor_time_in_seconds; } double ceres_wrapper_solver_summary_get_linear_solver_time_in_seconds( const ceres_solver_summary_t* summary) { if (!summary) return 0.0; return summary->summary.linear_solver_time_in_seconds; } void ceres_wrapper_solver_summary_get_full_report( const ceres_solver_summary_t* summary, char* report, int report_size) { if (!summary) return; if (report && report_size > 0) { std::string full_report = summary->summary.FullReport(); int copy_size = std::min(static_cast(full_report.size()), report_size - 1); std::strncpy(report, full_report.c_str(), copy_size); report[copy_size] = '\0'; } } // ============================================================================ // Solve with Options and Summary // ============================================================================ ceres_wrapper_error_code_t ceres_wrapper_solve( ceres_problem_t* problem, const ceres_solver_options_t* options, ceres_solver_summary_t* summary, char* error_message, int error_message_size) { if (!problem) { fprintf(stdout, "E20251230 ceres_wrapper_solve: ERROR - problem is null\n"); fflush(stdout); if (error_message && error_message_size > 0) { std::strncpy(error_message, "problem is null", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_NULL_POINTER; } if (!options) { fprintf(stdout, "E20251230 ceres_wrapper_solve: ERROR - options is null\n"); fflush(stdout); if (error_message && error_message_size > 0) { std::strncpy(error_message, "options is null", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_NULL_POINTER; } if (!summary) { fprintf(stdout, "E20251230 ceres_wrapper_solve: ERROR - summary is null\n"); fflush(stdout); if (error_message && error_message_size > 0) { std::strncpy(error_message, "summary is null", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_NULL_POINTER; } try { auto* ceres_problem = reinterpret_cast(problem); // Forward call to Ceres - with detailed logging ceres::Solve(options->options, ceres_problem, &summary->summary); return CERES_WRAPPER_SUCCESS; } catch (const std::exception& e) { fprintf(stdout, "E20251230 ceres_wrapper_solve: std::exception caught - what()=%s\n", e.what()); fflush(stdout); if (error_message && error_message_size > 0) { std::strncpy(error_message, e.what(), error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_EXCEPTION; } catch (...) { fprintf(stdout, "E20251230 ceres_wrapper_solve: Unknown exception caught\n"); fflush(stdout); if (error_message && error_message_size > 0) { std::strncpy(error_message, "Unknown exception during solve", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_EXCEPTION; } } // ============================================================================ // Problem Creation (Official C API) // ============================================================================ ceres_problem_t* ceres_create_problem() { try { return reinterpret_cast(new ceres::Problem()); } catch (...) { return nullptr; } } void ceres_free_problem(ceres_problem_t* problem) { if (problem) { delete reinterpret_cast(problem); } } // ============================================================================ // Parameter Blocks Management // ============================================================================ ceres_wrapper_error_code_t ceres_wrapper_problem_add_parameter_block( ceres_problem_t* problem, double* parameters, int size, char* error_message, int error_message_size) { if (!problem) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "problem is null", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_NULL_POINTER; } if (!parameters) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "parameters is null", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_NULL_POINTER; } if (size <= 0) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "size must be positive", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_INVALID_PARAMETER; } try { auto* ceres_problem = reinterpret_cast(problem); ceres_problem->AddParameterBlock(parameters, size); return CERES_WRAPPER_SUCCESS; } catch (const std::exception& e) { if (error_message && error_message_size > 0) { std::strncpy(error_message, e.what(), error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_EXCEPTION; } catch (...) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "Unknown exception", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_EXCEPTION; } } void ceres_wrapper_problem_set_parameter_block_constant( ceres_problem_t* problem, double* parameters) { if (!problem || !parameters) return; try { auto* ceres_problem = reinterpret_cast(problem); ceres_problem->SetParameterBlockConstant(parameters); } catch (...) { // Prevent C++ exception from crossing extern "C" boundary } } void ceres_wrapper_problem_set_parameter_block_variable( ceres_problem_t* problem, double* parameters) { if (!problem || !parameters) return; try { auto* ceres_problem = reinterpret_cast(problem); ceres_problem->SetParameterBlockVariable(parameters); } catch (...) { // Prevent C++ exception from crossing extern "C" boundary } } ceres_wrapper_error_code_t ceres_wrapper_problem_remove_parameter_block( ceres_problem_t* problem, double* parameters, char* error_message, int error_message_size) { if (!problem) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "problem is null", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_NULL_POINTER; } if (!parameters) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "parameters is null", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_NULL_POINTER; } try { auto* ceres_problem = reinterpret_cast(problem); ceres_problem->RemoveParameterBlock(parameters); return CERES_WRAPPER_SUCCESS; } catch (const std::exception& e) { if (error_message && error_message_size > 0) { std::strncpy(error_message, e.what(), error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_EXCEPTION; } catch (...) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "Unknown exception", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_EXCEPTION; } } ceres_wrapper_error_code_t ceres_wrapper_problem_remove_residual_block( ceres_problem_t* problem, ceres_residual_block_id_t* residual_block_id, char* error_message, int error_message_size) { if (!problem) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "problem is null", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_NULL_POINTER; } if (!residual_block_id) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "residual_block_id is null", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_NULL_POINTER; } try { auto* ceres_problem = reinterpret_cast(problem); auto* block_id = reinterpret_cast(residual_block_id); ceres_problem->RemoveResidualBlock(block_id); return CERES_WRAPPER_SUCCESS; } catch (const std::exception& e) { if (error_message && error_message_size > 0) { std::strncpy(error_message, e.what(), error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_EXCEPTION; } catch (...) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "Unknown exception", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_EXCEPTION; } } // ============================================================================ // Loss Functions (defined early for use in AddResidualBlock) // ============================================================================ struct ceres_wrapper_loss_function_s { std::unique_ptr loss; }; ceres_wrapper_error_code_t ceres_wrapper_problem_add_residual_block( ceres_problem_t* problem, void* cost_function, void* loss_function, double** parameter_blocks, int num_parameter_blocks, ceres_residual_block_id_t** residual_block_id, char* error_message, int error_message_size) { if (!problem) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "problem is null", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_NULL_POINTER; } if (!cost_function) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "cost_function is null", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_NULL_POINTER; } if (!parameter_blocks) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "parameter_blocks is null", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_NULL_POINTER; } if (num_parameter_blocks <= 0) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "num_parameter_blocks must be positive", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_INVALID_PARAMETER; } try { auto* ceres_problem = reinterpret_cast(problem); auto* ceres_cost = reinterpret_cast(cost_function); // Handle loss function ceres::LossFunction* ceres_loss = nullptr; if (loss_function) { auto* loss_wrapper = reinterpret_cast(loss_function); ceres_loss = loss_wrapper->loss.get(); // Don't release yet } // Add residual block // Problem takes ownership of cost_function and loss_function by default auto* block_id = ceres_problem->AddResidualBlock( ceres_cost, ceres_loss, parameter_blocks, num_parameter_blocks); // Only release ownership AFTER successful AddResidualBlock if (loss_function) { auto* loss_wrapper = reinterpret_cast(loss_function); loss_wrapper->loss.release(); // Now safe to release } // Set output parameter if provided if (residual_block_id) { *residual_block_id = reinterpret_cast(block_id); } return CERES_WRAPPER_SUCCESS; } catch (const std::exception& e) { if (error_message && error_message_size > 0) { std::strncpy(error_message, e.what(), error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_EXCEPTION; } catch (...) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "Unknown exception", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_EXCEPTION; } } int ceres_wrapper_problem_num_parameter_blocks( const ceres_problem_t* problem) { if (!problem) return 0; const auto* ceres_problem = reinterpret_cast(problem); return ceres_problem->NumParameterBlocks(); } int ceres_wrapper_problem_num_residual_blocks( const ceres_problem_t* problem) { if (!problem) return 0; const auto* ceres_problem = reinterpret_cast(problem); return ceres_problem->NumResidualBlocks(); } int ceres_wrapper_problem_num_parameters( const ceres_problem_t* problem) { if (!problem) return 0; const auto* ceres_problem = reinterpret_cast(problem); return ceres_problem->NumParameters(); } int ceres_wrapper_problem_num_residuals( const ceres_problem_t* problem) { if (!problem) return 0; const auto* ceres_problem = reinterpret_cast(problem); return ceres_problem->NumResiduals(); } // ============================================================================ // Loss Functions (struct already defined above for AddResidualBlock) // ============================================================================ ceres_wrapper_loss_function_t* ceres_wrapper_create_huber_loss(double a) { try { auto* loss = new ceres_wrapper_loss_function_s; loss->loss = std::make_unique(a); return loss; } catch (...) { return nullptr; } } ceres_wrapper_loss_function_t* ceres_wrapper_create_trivial_loss() { try { auto* loss = new ceres_wrapper_loss_function_s; loss->loss = std::make_unique(); return loss; } catch (...) { return nullptr; } } ceres_wrapper_loss_function_t* ceres_wrapper_create_cauchy_loss(double a) { try { auto* loss = new ceres_wrapper_loss_function_s; loss->loss = std::make_unique(a); return loss; } catch (...) { return nullptr; } } ceres_wrapper_loss_function_t* ceres_wrapper_create_softl1_loss(double a) { try { auto* loss = new ceres_wrapper_loss_function_s; loss->loss = std::make_unique(a); return loss; } catch (...) { return nullptr; } } ceres_wrapper_loss_function_t* ceres_wrapper_create_arctan_loss(double a) { try { auto* loss = new ceres_wrapper_loss_function_s; loss->loss = std::make_unique(a); return loss; } catch (...) { return nullptr; } } ceres_wrapper_loss_function_t* ceres_wrapper_create_tolerant_loss(double a, double b) { try { auto* loss = new ceres_wrapper_loss_function_s; loss->loss = std::make_unique(a, b); return loss; } catch (...) { return nullptr; } } void ceres_wrapper_free_loss_function(ceres_wrapper_loss_function_t* loss) { delete loss; } ceres_wrapper_loss_function_t* ceres_wrapper_create_composed_loss( ceres_wrapper_loss_function_t* f, int ownership_f, ceres_wrapper_loss_function_t* g, int ownership_g) { if (!f || !g) { return nullptr; } try { auto* loss = new ceres_wrapper_loss_function_s; ceres::LossFunction* ceres_f = f->loss.release(); ceres::LossFunction* ceres_g = g->loss.release(); ceres::Ownership ownership_f_enum = ownership_f ? ceres::TAKE_OWNERSHIP : ceres::DO_NOT_TAKE_OWNERSHIP; ceres::Ownership ownership_g_enum = ownership_g ? ceres::TAKE_OWNERSHIP : ceres::DO_NOT_TAKE_OWNERSHIP; try { loss->loss = std::make_unique( ceres_f, ownership_f_enum, ceres_g, ownership_g_enum); } catch (...) { // If ownership was TAKE_OWNERSHIP, ComposedLoss ctor would have taken ownership // But if it threw before, we need to clean up if (ownership_f_enum == ceres::DO_NOT_TAKE_OWNERSHIP) delete ceres_f; if (ownership_g_enum == ceres::DO_NOT_TAKE_OWNERSHIP) delete ceres_g; delete loss; delete f; delete g; return nullptr; } // Free the wrapper structures (we always release the unique_ptr above) delete f; delete g; return loss; } catch (...) { return nullptr; } } ceres_wrapper_loss_function_t* ceres_wrapper_create_scaled_loss( ceres_wrapper_loss_function_t* rho, double a, int ownership) { try { auto* loss = new ceres_wrapper_loss_function_s; // Extract loss function from wrapper // Note: We release from unique_ptr because ScaledLoss will manage ownership ceres::LossFunction* ceres_rho = nullptr; if (rho) { ceres_rho = rho->loss.release(); } // Create ScaledLoss // If ownership = 1, ScaledLoss takes ownership (will delete when done) // If ownership = 0, ScaledLoss does not take ownership (caller manages) ceres::Ownership ownership_enum = ownership ? ceres::TAKE_OWNERSHIP : ceres::DO_NOT_TAKE_OWNERSHIP; loss->loss = std::make_unique(ceres_rho, a, ownership_enum); // Free the wrapper structure (we always release the unique_ptr above if rho was provided) if (rho) { delete rho; } return loss; } catch (...) { return nullptr; } } // ============================================================================ // Manifolds // ============================================================================ // Custom deleter type for manifolds (allows non-owning pointers) using ManifoldDeleter = std::function; using ManifoldPtr = std::unique_ptr; struct ceres_manifold_s { ManifoldPtr manifold; }; ceres_manifold_t* ceres_wrapper_create_quaternion_manifold() { auto* m = new ceres_manifold_t; // Create with default deleter (owning) auto ptr = std::make_unique(); m->manifold = ManifoldPtr(ptr.release(), [](ceres::Manifold* p) { delete p; }); return m; } ceres_manifold_t* ceres_wrapper_create_sphere_manifold(int dimension) { auto* m = new ceres_manifold_t; // Create with default deleter (owning) auto ptr = std::make_unique>(dimension); m->manifold = ManifoldPtr(ptr.release(), [](ceres::Manifold* p) { delete p; }); return m; } ceres_manifold_t* ceres_wrapper_create_line_manifold(int dimension) { auto* m = new ceres_manifold_t; // LineManifold is a template class, use DYNAMIC for runtime dimension // Create with default deleter (owning) auto ptr = std::make_unique>(dimension); m->manifold = ManifoldPtr(ptr.release(), [](ceres::Manifold* p) { delete p; }); return m; } ceres_manifold_t* ceres_wrapper_create_euclidean_manifold(int dimension) { auto* m = new ceres_manifold_t; // EuclideanManifold is a template class, use DYNAMIC for runtime dimension auto ptr = std::make_unique>(dimension); m->manifold = ManifoldPtr(ptr.release(), [](ceres::Manifold* p) { delete p; }); return m; } ceres_manifold_t* ceres_wrapper_create_subset_manifold( const int* constant_subset, int constant_subset_size, int ambient_size) { if (!constant_subset || constant_subset_size <= 0 || ambient_size <= 0) { return nullptr; } auto* m = new ceres_manifold_t; // Convert C array to std::vector std::vector constant_indices(constant_subset, constant_subset + constant_subset_size); // SubsetManifold constructor: SubsetManifold(int size, const std::vector& constant_parameters) auto ptr = std::make_unique( ambient_size, constant_indices); m->manifold = ManifoldPtr(ptr.release(), [](ceres::Manifold* p) { delete p; }); return m; } void ceres_wrapper_free_manifold(ceres_manifold_t* manifold) { delete manifold; } void ceres_wrapper_problem_set_manifold( ceres_problem_t* problem, double* parameters, ceres_manifold_t* manifold) { auto* ceres_problem = reinterpret_cast(problem); // Problem takes ownership of the manifold (TAKE_OWNERSHIP by default) // We need to release the unique_ptr before passing to Problem ceres::Manifold* manifold_ptr = manifold->manifold.release(); ceres_problem->SetManifold(parameters, manifold_ptr); // Note: Problem now owns the manifold, it will be freed when problem is destroyed // We can still free the wrapper struct, but the manifold itself is owned by Problem } int ceres_wrapper_manifold_ambient_size(const ceres_manifold_t* manifold) { if (!manifold || !manifold->manifold) return 0; return manifold->manifold->AmbientSize(); } int ceres_wrapper_manifold_tangent_size(const ceres_manifold_t* manifold) { if (!manifold || !manifold->manifold) return 0; return manifold->manifold->TangentSize(); } // ============================================================================ // AutoDiff Manifold // ============================================================================ // Wrapper class to bridge C callbacks to Ceres AutoDiffManifold // Since AutoDiffManifold is a template class, we create a custom Manifold // that uses callbacks and numeric differentiation for Jacobians class AutoDiffManifoldWrapper : public ceres::Manifold { public: AutoDiffManifoldWrapper( int ambient_size, int tangent_size, ceres_autodiff_manifold_plus_t plus_callback, ceres_autodiff_manifold_minus_t minus_callback, void* user_data) : ambient_size_(ambient_size), tangent_size_(tangent_size), plus_callback_(plus_callback), minus_callback_(minus_callback), user_data_(user_data) { if (ambient_size <= 0 || tangent_size <= 0) { throw std::invalid_argument("Ambient size and tangent size must be positive"); } } int AmbientSize() const override { return ambient_size_; } int TangentSize() const override { return tangent_size_; } bool Plus(const double* x, const double* delta, double* x_plus_delta) const override { if (!plus_callback_) { return false; } // user_data_ may legitimately be NULL if callback doesn't need state int result = plus_callback_(user_data_, x, delta, x_plus_delta); return (result != 0); } bool PlusJacobian(const double* x, double* jacobian) const override { // Use numeric differentiation for PlusJacobian const double kEpsilon = 1e-8; std::vector zero_delta(tangent_size_, 0.0); std::vector x_plus_delta(ambient_size_); std::vector perturbed_delta(tangent_size_); std::vector perturbed_x_plus_delta(ambient_size_); // Compute Plus(x, 0) as baseline if (!Plus(x, zero_delta.data(), x_plus_delta.data())) { return false; } // Compute Jacobian column by column for (int j = 0; j < tangent_size_; ++j) { // Perturb delta[j] std::copy(zero_delta.data(), zero_delta.data() + tangent_size_, perturbed_delta.data()); perturbed_delta[j] = kEpsilon; // Compute Plus(x, perturbed_delta) if (!Plus(x, perturbed_delta.data(), perturbed_x_plus_delta.data())) { return false; } // Compute finite difference: (Plus(x, perturbed_delta) - Plus(x, 0)) / epsilon for (int i = 0; i < ambient_size_; ++i) { jacobian[i * tangent_size_ + j] = (perturbed_x_plus_delta[i] - x_plus_delta[i]) / kEpsilon; } } return true; } bool Minus(const double* y, const double* x, double* y_minus_x) const override { if (!minus_callback_) { return false; } // user_data_ may legitimately be NULL if callback doesn't need state int result = minus_callback_(user_data_, y, x, y_minus_x); return (result != 0); } bool MinusJacobian(const double* x, double* jacobian) const override { // Use numeric differentiation for MinusJacobian // MinusJacobian is D_1 Minus(x, x), so we compute derivative w.r.t. first argument const double kEpsilon = 1e-8; std::vector y_minus_x(tangent_size_); std::vector perturbed_y(ambient_size_); std::vector perturbed_y_minus_x(tangent_size_); // Compute Minus(x, x) as baseline (should be zero) if (!Minus(x, x, y_minus_x.data())) { return false; } // Compute Jacobian column by column for (int j = 0; j < ambient_size_; ++j) { // Perturb y[j] (first argument) std::copy(x, x + ambient_size_, perturbed_y.data()); perturbed_y[j] += kEpsilon; // Compute Minus(perturbed_y, x) if (!Minus(perturbed_y.data(), x, perturbed_y_minus_x.data())) { return false; } // Compute finite difference: (Minus(perturbed_y, x) - Minus(x, x)) / epsilon for (int i = 0; i < tangent_size_; ++i) { jacobian[i * ambient_size_ + j] = (perturbed_y_minus_x[i] - y_minus_x[i]) / kEpsilon; } } return true; } private: int ambient_size_; int tangent_size_; ceres_autodiff_manifold_plus_t plus_callback_; ceres_autodiff_manifold_minus_t minus_callback_; void* user_data_; }; ceres_manifold_t* ceres_wrapper_create_autodiff_manifold( int ambient_size, int tangent_size, ceres_autodiff_manifold_plus_t plus_callback, ceres_autodiff_manifold_minus_t minus_callback, void* user_data) { if (ambient_size <= 0 || tangent_size <= 0) { return nullptr; } if (!plus_callback || !minus_callback) { return nullptr; } try { auto* m = new ceres_manifold_t; auto wrapper = std::make_unique( ambient_size, tangent_size, plus_callback, minus_callback, user_data); m->manifold = ManifoldPtr(wrapper.release(), [](ceres::Manifold* p) { delete p; }); return m; } catch (...) { return nullptr; } } void ceres_wrapper_free_autodiff_manifold(ceres_manifold_t* manifold) { // Same as free_manifold - just delete the wrapper struct // The manifold itself might be owned by Problem delete manifold; } // ============================================================================ // BiCubic Interpolator // ============================================================================ // Adapter for 2D grid data to work with Ceres BiCubicInterpolator class GridArrayAdapter { public: enum { DATA_DIMENSION = 1 }; GridArrayAdapter(const double* data, int rows, int cols) : data_(data), rows_(rows), cols_(cols) {} void GetValue(const int row, const int column, double* const value) const { if (row < 0 || column < 0 || row >= rows_ || column >= cols_) { *value = 0.0; // Out of bounds - return default value } else { *value = data_[row * cols_ + column]; } } int NumRows() const { return rows_; } int NumCols() const { return cols_; } private: const double* data_; int rows_; int cols_; }; struct ceres_bicubic_interpolator_s { std::unique_ptr> interpolator; std::unique_ptr adapter; std::vector data; // Keep data alive }; ceres_bicubic_interpolator_t* ceres_wrapper_create_bicubic_interpolator( const double* data, int rows, int cols) { if (!data || rows <= 0 || cols <= 0) { return nullptr; } auto* interp = new ceres_bicubic_interpolator_t; // Copy data to keep it alive interp->data.assign(data, data + rows * cols); // Create adapter with copied data interp->adapter = std::make_unique( interp->data.data(), rows, cols); // Create interpolator with adapter interp->interpolator = std::make_unique>( *interp->adapter); return interp; } void ceres_wrapper_free_bicubic_interpolator( ceres_bicubic_interpolator_t* interpolator) { delete interpolator; } void ceres_wrapper_bicubic_interpolator_evaluate( const ceres_bicubic_interpolator_t* interpolator, double x, double y, double* value, double* gradient_x, double* gradient_y) { double val = 0.0; double grad_r = 0.0; // gradient with respect to row double grad_c = 0.0; // gradient with respect to column // Evaluate: (row, column) = (y, x) // Note: Ceres uses (r, c) = (row, column) convention interpolator->interpolator->Evaluate(y, x, &val, &grad_r, &grad_c); if (value) *value = val; // Map gradients: grad_r (row) -> grad_y, grad_c (column) -> grad_x if (gradient_x) *gradient_x = grad_c; if (gradient_y) *gradient_y = grad_r; } // ============================================================================ // AutoDiff Cost Function Wrapper // ============================================================================ // Wrapper class to bridge C callback to Ceres AutoDiffCostFunction class AutoDiffCostFunctionWrapper : public ceres::CostFunction { public: AutoDiffCostFunctionWrapper( ceres_autodiff_cost_function_callback_t callback, void* user_data, int num_residuals, int num_parameter_blocks, const int* parameter_block_sizes) : callback_(callback), user_data_(user_data) { set_num_residuals(num_residuals); for (int i = 0; i < num_parameter_blocks; ++i) { mutable_parameter_block_sizes()->push_back(parameter_block_sizes[i]); } } bool Evaluate(double const* const* parameters, double* residuals, double** jacobians) const override { // Validate callback pointer (user_data_ may legitimately be NULL) if (!callback_) { return false; } // Call the C callback to get residuals int result = callback_(user_data_, parameters, residuals); if (result == 0) { return false; } // Basic validation: check for NaN/Inf in residuals for (int i = 0; i < num_residuals(); ++i) { if (std::isnan(residuals[i]) || std::isinf(residuals[i])) { return false; } } // If jacobians are requested, compute them using finite differences // Note: True AutoDiff requires template magic, so we use numerical differentiation if (jacobians != nullptr) { const double kEpsilon = 1e-4; std::vector perturbed_residuals(num_residuals()); int num_blocks = static_cast(parameter_block_sizes().size()); for (int i = 0; i < num_blocks; ++i) { if (jacobians[i] != nullptr) { int param_size = parameter_block_sizes()[i]; std::vector perturbed_params(param_size); // Create array of pointers to parameter blocks // All blocks point to original parameters except block i which points to perturbed_params std::vector perturbed_param_ptrs(num_blocks); for (int k = 0; k < num_blocks; ++k) { perturbed_param_ptrs[k] = parameters[k]; } for (int j = 0; j < param_size; ++j) { // Perturb parameter std::copy(parameters[i], parameters[i] + param_size, perturbed_params.data()); perturbed_params[j] += kEpsilon; // Update pointer for block i to point to perturbed data perturbed_param_ptrs[i] = perturbed_params.data(); // Evaluate with perturbed parameter (pass array of all parameter block pointers) int perturb_result = callback_(user_data_, perturbed_param_ptrs.data(), perturbed_residuals.data()); if (perturb_result == 0) { return false; } // Check for NaN/Inf in perturbed residuals for (int k = 0; k < num_residuals(); ++k) { if (std::isnan(perturbed_residuals[k]) || std::isinf(perturbed_residuals[k])) { return false; } } // Compute finite difference for (int k = 0; k < num_residuals(); ++k) { double diff = (perturbed_residuals[k] - residuals[k]) / kEpsilon; if (std::isnan(diff) || std::isinf(diff)) { return false; } jacobians[i][k * param_size + j] = diff; } } } } } return true; } private: ceres_autodiff_cost_function_callback_t callback_; void* user_data_; }; void* ceres_wrapper_create_autodiff_cost_function( ceres_autodiff_cost_function_callback_t callback, void* user_data, int num_residuals, int num_parameter_blocks, const int* parameter_block_sizes) { return new AutoDiffCostFunctionWrapper( callback, user_data, num_residuals, num_parameter_blocks, parameter_block_sizes); } void ceres_wrapper_free_autodiff_cost_function(void* cost_function) { delete reinterpret_cast(cost_function); } // ============================================================================ // Dynamic AutoDiff Cost Function Wrapper // ============================================================================ // Wrapper class to bridge C callback to Ceres DynamicAutoDiffCostFunction class DynamicAutoDiffCostFunctionWrapper : public ceres::DynamicCostFunction { public: DynamicAutoDiffCostFunctionWrapper( ceres_autodiff_cost_function_callback_t callback, void* user_data, int num_residuals, int num_parameter_blocks, const int* parameter_block_sizes) : callback_(callback), user_data_(user_data) { set_num_residuals(num_residuals); for (int i = 0; i < num_parameter_blocks; ++i) { mutable_parameter_block_sizes()->push_back(parameter_block_sizes[i]); } } bool Evaluate(double const* const* parameters, double* residuals, double** jacobians) const override { // Validate callback pointer (user_data_ may legitimately be NULL) if (!callback_) { return false; } // Call the C callback to get residuals int result = callback_(user_data_, parameters, residuals); if (result == 0) { return false; } // Basic validation: check for NaN/Inf in residuals for (int i = 0; i < num_residuals(); ++i) { if (std::isnan(residuals[i]) || std::isinf(residuals[i])) { return false; } } // If jacobians are requested, compute them using finite differences if (jacobians != nullptr) { const double kEpsilon = 1e-4; std::vector perturbed_residuals(num_residuals()); for (int i = 0; i < static_cast(parameter_block_sizes().size()); ++i) { if (jacobians[i] != nullptr) { int param_size = parameter_block_sizes()[i]; std::vector perturbed_params(param_size); // Create array of pointers for all parameter blocks std::vector param_ptrs(parameter_block_sizes().size()); for (int k = 0; k < static_cast(parameter_block_sizes().size()); ++k) { param_ptrs[k] = parameters[k]; } for (int j = 0; j < param_size; ++j) { // Perturb parameter std::copy(parameters[i], parameters[i] + param_size, perturbed_params.data()); perturbed_params[j] += kEpsilon; param_ptrs[i] = perturbed_params.data(); // Evaluate with perturbed parameter int perturb_result = callback_(user_data_, param_ptrs.data(), perturbed_residuals.data()); if (perturb_result == 0) { return false; } // Check for NaN/Inf in perturbed residuals for (int k = 0; k < num_residuals(); ++k) { if (std::isnan(perturbed_residuals[k]) || std::isinf(perturbed_residuals[k])) { return false; } } // Compute finite difference for (int k = 0; k < num_residuals(); ++k) { double diff = (perturbed_residuals[k] - residuals[k]) / kEpsilon; if (std::isnan(diff) || std::isinf(diff)) { return false; } jacobians[i][k * param_size + j] = diff; } } } } } return true; } private: ceres_autodiff_cost_function_callback_t callback_; void* user_data_; }; void* ceres_wrapper_create_dynamic_autodiff_cost_function( ceres_autodiff_cost_function_callback_t callback, void* user_data, int num_residuals, int num_parameter_blocks, const int* parameter_block_sizes) { return new DynamicAutoDiffCostFunctionWrapper( callback, user_data, num_residuals, num_parameter_blocks, parameter_block_sizes); } void ceres_wrapper_free_dynamic_autodiff_cost_function(void* cost_function) { delete reinterpret_cast(cost_function); } // ============================================================================ // Product Manifold // ============================================================================ // Custom manifold wrapper for runtime ProductManifold // Since Ceres ProductManifold requires compile-time template parameters, // we create a custom manifold that wraps multiple manifolds at runtime class RuntimeProductManifold : public ceres::Manifold { public: RuntimeProductManifold(std::vector manifolds) : manifolds_(std::move(manifolds)) { // Calculate sizes ambient_size_ = 0; tangent_size_ = 0; for (const auto& m : manifolds_) { ambient_sizes_.push_back(m->AmbientSize()); tangent_sizes_.push_back(m->TangentSize()); ambient_size_ += ambient_sizes_.back(); tangent_size_ += tangent_sizes_.back(); } // Calculate offsets ambient_offsets_.push_back(0); tangent_offsets_.push_back(0); for (size_t i = 0; i < manifolds_.size(); ++i) { ambient_offsets_.push_back(ambient_offsets_.back() + ambient_sizes_[i]); tangent_offsets_.push_back(tangent_offsets_.back() + tangent_sizes_[i]); } } int AmbientSize() const override { return ambient_size_; } int TangentSize() const override { return tangent_size_; } bool Plus(const double* x, const double* delta, double* x_plus_delta) const override { for (size_t i = 0; i < manifolds_.size(); ++i) { // Validate manifold pointer to prevent use-after-free if (!manifolds_[i]) { return false; } const double* x_i = x + ambient_offsets_[i]; const double* delta_i = delta + tangent_offsets_[i]; double* x_plus_delta_i = x_plus_delta + ambient_offsets_[i]; if (!manifolds_[i]->Plus(x_i, delta_i, x_plus_delta_i)) { return false; } } return true; } bool Minus(const double* y, const double* x, double* y_minus_x) const override { for (size_t i = 0; i < manifolds_.size(); ++i) { // Validate manifold pointer to prevent use-after-free if (!manifolds_[i]) { return false; } const double* y_i = y + ambient_offsets_[i]; const double* x_i = x + ambient_offsets_[i]; double* y_minus_x_i = y_minus_x + tangent_offsets_[i]; if (!manifolds_[i]->Minus(y_i, x_i, y_minus_x_i)) { return false; } } return true; } bool PlusJacobian(const double* x, double* jacobian) const override { // Compute Jacobian for Plus operation // For product manifold, Jacobian is block diagonal // Jacobian is AmbientSize() x TangentSize() matrix (row-major) // Each block is ambient_sizes_[i] x tangent_sizes_[i] // Initialize to zero std::fill(jacobian, jacobian + ambient_size_ * tangent_size_, 0.0); for (size_t i = 0; i < manifolds_.size(); ++i) { const double* x_i = x + ambient_offsets_[i]; // Get sub-jacobian for this manifold std::vector sub_jacobian(ambient_sizes_[i] * tangent_sizes_[i]); if (!manifolds_[i]->PlusJacobian(x_i, sub_jacobian.data())) { return false; } // Copy sub-jacobian to correct position in block diagonal for (int row = 0; row < ambient_sizes_[i]; ++row) { for (int col = 0; col < tangent_sizes_[i]; ++col) { jacobian[(ambient_offsets_[i] + row) * tangent_size_ + tangent_offsets_[i] + col] = sub_jacobian[row * tangent_sizes_[i] + col]; } } } return true; } bool MinusJacobian(const double* x, double* jacobian) const override { // Compute Jacobian for Minus operation // For product manifold, Jacobian is block diagonal // Jacobian is TangentSize() x AmbientSize() matrix (row-major) // Each block is tangent_sizes_[i] x ambient_sizes_[i] // Initialize to zero std::fill(jacobian, jacobian + tangent_size_ * ambient_size_, 0.0); for (size_t i = 0; i < manifolds_.size(); ++i) { const double* x_i = x + ambient_offsets_[i]; // Get sub-jacobian for this manifold std::vector sub_jacobian(tangent_sizes_[i] * ambient_sizes_[i]); if (!manifolds_[i]->MinusJacobian(x_i, sub_jacobian.data())) { return false; } // Copy sub-jacobian to correct position in block diagonal for (int row = 0; row < tangent_sizes_[i]; ++row) { for (int col = 0; col < ambient_sizes_[i]; ++col) { jacobian[(tangent_offsets_[i] + row) * ambient_size_ + ambient_offsets_[i] + col] = sub_jacobian[row * ambient_sizes_[i] + col]; } } } return true; } private: std::vector manifolds_; int ambient_size_; int tangent_size_; std::vector ambient_sizes_; std::vector tangent_sizes_; std::vector ambient_offsets_; std::vector tangent_offsets_; }; ceres_manifold_t* ceres_wrapper_create_product_manifold( const ceres_manifold_t** manifolds, int num_manifolds) { if (!manifolds || num_manifolds <= 0) { return nullptr; } // Extract and clone Ceres manifolds from wrappers // We need to clone because we'll own them std::vector manifold_clones; for (int i = 0; i < num_manifolds; ++i) { if (!manifolds[i] || !manifolds[i]->manifold) { return nullptr; // Invalid manifold } // We need to clone the manifold, but since we can't clone generically, // we'll just reference the original. This means the original manifold // must outlive the product manifold. // For a proper implementation, we'd need to clone based on type. // For now, we'll use a non-owning unique_ptr with custom deleter // WARNING: Non-owning reference - the original manifold wrappers must outlive // this product manifold. Freeing a component manifold while the product manifold // is alive will result in undefined behavior (dangling pointer). ceres::Manifold* raw_ptr = manifolds[i]->manifold.get(); manifold_clones.push_back(ManifoldPtr( raw_ptr, [](ceres::Manifold*) {})); // Non-owning deleter } // Create runtime product manifold auto* wrapped = new ceres_manifold_t; // Create with default deleter (owning) auto ptr = std::make_unique(std::move(manifold_clones)); wrapped->manifold = ManifoldPtr(ptr.release(), [](ceres::Manifold* p) { delete p; }); return wrapped; } // ============================================================================ // Problem Query Methods // ============================================================================ int ceres_wrapper_problem_has_parameter_block( const ceres_problem_t* problem, const double* parameters) { if (!problem || !parameters) { return 0; } const auto* ceres_problem = reinterpret_cast(problem); return ceres_problem->HasParameterBlock(parameters) ? 1 : 0; } int ceres_wrapper_problem_is_parameter_block_constant( const ceres_problem_t* problem, const double* parameters) { if (!problem || !parameters) { return 0; } const auto* ceres_problem = reinterpret_cast(problem); return ceres_problem->IsParameterBlockConstant(parameters) ? 1 : 0; } int ceres_wrapper_problem_get_parameter_block_size( const ceres_problem_t* problem, const double* parameters) { if (!problem || !parameters) { return -1; } const auto* ceres_problem = reinterpret_cast(problem); if (!ceres_problem->HasParameterBlock(parameters)) { return -1; } return ceres_problem->ParameterBlockSize(parameters); } int ceres_wrapper_problem_get_parameter_block_tangent_size( const ceres_problem_t* problem, const double* parameters) { if (!problem || !parameters) { return -1; } const auto* ceres_problem = reinterpret_cast(problem); if (!ceres_problem->HasParameterBlock(parameters)) { return -1; } return ceres_problem->ParameterBlockTangentSize(parameters); } int ceres_wrapper_problem_has_manifold( const ceres_problem_t* problem, const double* parameters) { if (!problem || !parameters) { return 0; } const auto* ceres_problem = reinterpret_cast(problem); return ceres_problem->HasManifold(parameters) ? 1 : 0; } ceres_manifold_t* ceres_wrapper_problem_get_manifold( const ceres_problem_t* problem, const double* parameters) { if (!problem || !parameters) { return nullptr; } const auto* ceres_problem = reinterpret_cast(problem); const ceres::Manifold* ceres_manifold = ceres_problem->GetManifold(parameters); if (!ceres_manifold) { return nullptr; } // Wrap the manifold (note: we don't own it, so we create a non-owning wrapper) // However, since Problem owns the manifold, we need to be careful // For now, we'll create a wrapper that doesn't own the pointer // This is a limitation - the returned manifold should not be freed auto* wrapped = new ceres_manifold_t; // Create a unique_ptr that doesn't delete (since Problem owns it) wrapped->manifold = ManifoldPtr( const_cast(ceres_manifold), [](ceres::Manifold*) {}); // No-op deleter return wrapped; } // ============================================================================ // Iteration Callback // ============================================================================ void ceres_wrapper_solver_options_set_iteration_callback( ceres_solver_options_t* options, ceres_iteration_callback_t callback, void* user_data) { if (!options) { return; } // Clear existing callback options->options.callbacks.clear(); options->callback_wrapper.reset(); if (callback) { // Create callback wrapper and store it in options options->callback_wrapper = std::make_unique(callback, user_data); options->options.callbacks.push_back(options->callback_wrapper.get()); } } // ============================================================================ // Numeric Differentiation Cost Functions (Phase 2) // ============================================================================ // Note: ConvertNumericDiffMethod is no longer needed since we use template specialization // Keeping it commented out in case it's needed in the future // static ceres::NumericDiffMethodType ConvertNumericDiffMethod(ceres_numeric_diff_method_t method) { // switch (method) { // case CERES_NUMERIC_DIFF_FORWARD: // return ceres::FORWARD; // case CERES_NUMERIC_DIFF_CENTRAL: // return ceres::CENTRAL; // case CERES_NUMERIC_DIFF_RIDDERS: // return ceres::RIDDERS; // default: // return ceres::CENTRAL; // } // } // Cost functor wrapper for NumericDiffCostFunction // This functor bridges the C callback to Ceres' template-based NumericDiffCostFunction struct NumericDiffCostFunctor { NumericDiffCostFunctor(ceres_numeric_diff_cost_function_t callback, void* user_data) : callback_(callback), user_data_(user_data) {} // Template operator for Ceres - converts T* to double* for C callback // For numeric diff, T is always double, so we can safely cast template bool operator()(const T* const* parameters, T* residuals) const { // Validate callback pointer (user_data_ may legitimately be NULL) if (!callback_) { return false; } // For numeric diff, T is always double, so we can safely cast // Convert const T* const* to double* const* for C callback // Note: C callback expects double* const* (non-const pointers to const data) // We use const_cast which is safe here since numeric diff only reads double* const* params_for_callback = const_cast(reinterpret_cast(parameters)); double* double_residuals = reinterpret_cast(residuals); int result = callback_(params_for_callback, double_residuals, user_data_); return (result != 0); } private: ceres_numeric_diff_cost_function_t callback_; void* user_data_; }; void* ceres_wrapper_create_numeric_diff_cost_function( const ceres_numeric_diff_options_t* options, ceres_numeric_diff_method_t method) { if (!options || !options->callback) { return nullptr; } // Create functor wrapper auto* functor = new NumericDiffCostFunctor(options->callback, options->user_data); // Use DynamicNumericDiffCostFunction for flexibility with variable parameter block sizes // The method type is a template parameter, so we need to create the appropriate specialization ceres::NumericDiffOptions numeric_diff_options; // Create cost function based on method type using template specialization ceres::CostFunction* cost_function = nullptr; switch (method) { case CERES_NUMERIC_DIFF_FORWARD: { auto* cf = new ceres::DynamicNumericDiffCostFunction( functor, ceres::TAKE_OWNERSHIP, numeric_diff_options); cf->SetNumResiduals(options->num_residuals); for (int i = 0; i < options->num_parameter_blocks; ++i) { cf->AddParameterBlock(options->parameter_block_sizes[i]); } cost_function = cf; break; } case CERES_NUMERIC_DIFF_CENTRAL: { auto* cf = new ceres::DynamicNumericDiffCostFunction( functor, ceres::TAKE_OWNERSHIP, numeric_diff_options); cf->SetNumResiduals(options->num_residuals); for (int i = 0; i < options->num_parameter_blocks; ++i) { cf->AddParameterBlock(options->parameter_block_sizes[i]); } cost_function = cf; break; } case CERES_NUMERIC_DIFF_RIDDERS: { auto* cf = new ceres::DynamicNumericDiffCostFunction( functor, ceres::TAKE_OWNERSHIP, numeric_diff_options); cf->SetNumResiduals(options->num_residuals); for (int i = 0; i < options->num_parameter_blocks; ++i) { cf->AddParameterBlock(options->parameter_block_sizes[i]); } cost_function = cf; break; } default: { // Default to CENTRAL auto* cf = new ceres::DynamicNumericDiffCostFunction( functor, ceres::TAKE_OWNERSHIP, numeric_diff_options); cf->SetNumResiduals(options->num_residuals); for (int i = 0; i < options->num_parameter_blocks; ++i) { cf->AddParameterBlock(options->parameter_block_sizes[i]); } cost_function = cf; break; } } return cost_function; } void* ceres_wrapper_create_dynamic_numeric_diff_cost_function( const ceres_numeric_diff_options_t* options, ceres_numeric_diff_method_t method) { // Same implementation - DynamicNumericDiffCostFunction already handles dynamic sizes return ceres_wrapper_create_numeric_diff_cost_function(options, method); } void ceres_wrapper_free_numeric_diff_cost_function(void* cost_function) { if (cost_function) { // Delete the CostFunction (base class) // Note: The functor is owned by the cost function, so it will be deleted automatically delete reinterpret_cast(cost_function); } } // ============================================================================ // Additional SolverOptions (Phase 2) // ============================================================================ void ceres_wrapper_solver_options_set_line_search_type( ceres_solver_options_t* options, int line_search_type) { if (options) { options->options.line_search_type = static_cast(line_search_type); } } int ceres_wrapper_solver_options_get_line_search_type( const ceres_solver_options_t* options) { return options ? static_cast(options->options.line_search_type) : 0; } void ceres_wrapper_solver_options_set_line_search_direction_type( ceres_solver_options_t* options, int line_search_direction_type) { if (options) { options->options.line_search_direction_type = static_cast(line_search_direction_type); } } int ceres_wrapper_solver_options_get_line_search_direction_type( const ceres_solver_options_t* options) { return options ? static_cast(options->options.line_search_direction_type) : 0; } void ceres_wrapper_solver_options_set_nonlinear_conjugate_gradient_type( ceres_solver_options_t* options, int ncg_type) { if (options) { options->options.nonlinear_conjugate_gradient_type = static_cast(ncg_type); } } int ceres_wrapper_solver_options_get_nonlinear_conjugate_gradient_type( const ceres_solver_options_t* options) { return options ? static_cast(options->options.nonlinear_conjugate_gradient_type) : 0; } void ceres_wrapper_solver_options_set_max_lbfgs_rank( ceres_solver_options_t* options, int max_lbfgs_rank) { if (options) { options->options.max_lbfgs_rank = max_lbfgs_rank; } } int ceres_wrapper_solver_options_get_max_lbfgs_rank( const ceres_solver_options_t* options) { return options ? options->options.max_lbfgs_rank : 0; } void ceres_wrapper_solver_options_set_max_line_search_step_contraction( ceres_solver_options_t* options, double max_step_contraction) { if (options) { options->options.max_line_search_step_contraction = max_step_contraction; } } double ceres_wrapper_solver_options_get_max_line_search_step_contraction( const ceres_solver_options_t* options) { return options ? options->options.max_line_search_step_contraction : 0.0; } void ceres_wrapper_solver_options_set_min_line_search_step_contraction( ceres_solver_options_t* options, double min_step_contraction) { if (options) { options->options.min_line_search_step_contraction = min_step_contraction; } } double ceres_wrapper_solver_options_get_min_line_search_step_contraction( const ceres_solver_options_t* options) { return options ? options->options.min_line_search_step_contraction : 0.0; } void ceres_wrapper_solver_options_set_max_num_line_search_step_size_iterations( ceres_solver_options_t* options, int max_iterations) { if (options) { options->options.max_num_line_search_step_size_iterations = max_iterations; } } int ceres_wrapper_solver_options_get_max_num_line_search_step_size_iterations( const ceres_solver_options_t* options) { return options ? options->options.max_num_line_search_step_size_iterations : 0; } void ceres_wrapper_solver_options_set_max_num_line_search_direction_restarts( ceres_solver_options_t* options, int max_restarts) { if (options) { options->options.max_num_line_search_direction_restarts = max_restarts; } } int ceres_wrapper_solver_options_get_max_num_line_search_direction_restarts( const ceres_solver_options_t* options) { return options ? options->options.max_num_line_search_direction_restarts : 0; } void ceres_wrapper_solver_options_set_line_search_sufficient_function_decrease( ceres_solver_options_t* options, double sufficient_decrease) { if (options) { options->options.line_search_sufficient_function_decrease = sufficient_decrease; } } double ceres_wrapper_solver_options_get_line_search_sufficient_function_decrease( const ceres_solver_options_t* options) { return options ? options->options.line_search_sufficient_function_decrease : 0.0; } void ceres_wrapper_solver_options_set_line_search_sufficient_curvature_decrease( ceres_solver_options_t* options, double sufficient_curvature_decrease) { if (options) { options->options.line_search_sufficient_curvature_decrease = sufficient_curvature_decrease; } } double ceres_wrapper_solver_options_get_line_search_sufficient_curvature_decrease( const ceres_solver_options_t* options) { return options ? options->options.line_search_sufficient_curvature_decrease : 0.0; } void ceres_wrapper_solver_options_set_max_line_search_step_expansion( ceres_solver_options_t* options, double max_step_expansion) { if (options) { options->options.max_line_search_step_expansion = max_step_expansion; } } double ceres_wrapper_solver_options_get_max_line_search_step_expansion( const ceres_solver_options_t* options) { return options ? options->options.max_line_search_step_expansion : 0.0; } void ceres_wrapper_solver_options_set_max_lm_diagonal( ceres_solver_options_t* options, double max_lm_diagonal) { if (options) { options->options.max_lm_diagonal = max_lm_diagonal; } } double ceres_wrapper_solver_options_get_max_lm_diagonal( const ceres_solver_options_t* options) { return options ? options->options.max_lm_diagonal : 0.0; } void ceres_wrapper_solver_options_set_min_lm_diagonal( ceres_solver_options_t* options, double min_lm_diagonal) { if (options) { options->options.min_lm_diagonal = min_lm_diagonal; } } double ceres_wrapper_solver_options_get_min_lm_diagonal( const ceres_solver_options_t* options) { return options ? options->options.min_lm_diagonal : 0.0; } void ceres_wrapper_solver_options_set_sparse_linear_algebra_library_type( ceres_solver_options_t* options, int sparse_library_type) { if (options) { options->options.sparse_linear_algebra_library_type = static_cast(sparse_library_type); } } int ceres_wrapper_solver_options_get_sparse_linear_algebra_library_type( const ceres_solver_options_t* options) { return options ? static_cast(options->options.sparse_linear_algebra_library_type) : 0; } void ceres_wrapper_solver_options_set_dense_linear_algebra_library_type( ceres_solver_options_t* options, int dense_library_type) { if (options) { options->options.dense_linear_algebra_library_type = static_cast(dense_library_type); } } int ceres_wrapper_solver_options_get_dense_linear_algebra_library_type( const ceres_solver_options_t* options) { return options ? static_cast(options->options.dense_linear_algebra_library_type) : 0; } void ceres_wrapper_solver_options_set_max_linear_solver_iterations( ceres_solver_options_t* options, int max_iterations) { if (options) { options->options.max_linear_solver_iterations = max_iterations; } } int ceres_wrapper_solver_options_get_max_linear_solver_iterations( const ceres_solver_options_t* options) { return options ? options->options.max_linear_solver_iterations : 0; } void ceres_wrapper_solver_options_set_min_linear_solver_iterations( ceres_solver_options_t* options, int min_iterations) { if (options) { options->options.min_linear_solver_iterations = min_iterations; } } int ceres_wrapper_solver_options_get_min_linear_solver_iterations( const ceres_solver_options_t* options) { return options ? options->options.min_linear_solver_iterations : 0; } // Note: linear_solver_tolerance doesn't exist in Ceres Solver::Options // This is a placeholder - remove or comment out if not available void ceres_wrapper_solver_options_set_linear_solver_tolerance( ceres_solver_options_t* options, double tolerance) { // This option doesn't exist in Ceres - no-op (void)options; (void)tolerance; } double ceres_wrapper_solver_options_get_linear_solver_tolerance( const ceres_solver_options_t* options) { // This option doesn't exist in Ceres - return 0 (void)options; return 0.0; } void ceres_wrapper_solver_options_set_use_inner_iterations( ceres_solver_options_t* options, int use_inner_iterations) { if (options) { options->options.use_inner_iterations = (use_inner_iterations != 0); } } int ceres_wrapper_solver_options_get_use_inner_iterations( const ceres_solver_options_t* options) { return options ? (options->options.use_inner_iterations ? 1 : 0) : 0; } void ceres_wrapper_solver_options_set_inner_iteration_tolerance( ceres_solver_options_t* options, double tolerance) { if (options) { options->options.inner_iteration_tolerance = tolerance; } } double ceres_wrapper_solver_options_get_inner_iteration_tolerance( const ceres_solver_options_t* options) { return options ? options->options.inner_iteration_tolerance : 0.0; } void ceres_wrapper_solver_options_set_max_solver_time_in_seconds( ceres_solver_options_t* options, double max_time) { if (options) { options->options.max_solver_time_in_seconds = max_time; } } double ceres_wrapper_solver_options_get_max_solver_time_in_seconds( const ceres_solver_options_t* options) { return options ? options->options.max_solver_time_in_seconds : 0.0; } void ceres_wrapper_solver_options_set_update_state_every_iteration( ceres_solver_options_t* options, int update_every_iteration) { if (options) { options->options.update_state_every_iteration = (update_every_iteration != 0); } } int ceres_wrapper_solver_options_get_update_state_every_iteration( const ceres_solver_options_t* options) { return options ? (options->options.update_state_every_iteration ? 1 : 0) : 0; } // ============================================================================ // Problem Options (Phase 2) // ============================================================================ struct ceres_problem_options_s { ceres::Problem::Options options; }; // Evaluation callback wrapper class // Note: We need to store the wrapper to keep it alive // We'll use a map to track callbacks per options object // Thread-safe: protected by mutex static std::mutex evaluation_callbacks_mutex; static std::map> evaluation_callbacks; class EvaluationCallbackWrapper : public ceres::EvaluationCallback { public: EvaluationCallbackWrapper( ceres_evaluation_callback_t callback, void* user_data) : callback_(callback), user_data_(user_data) {} void PrepareForEvaluation(bool evaluate_jacobians, bool new_evaluation_point) override { (void)evaluate_jacobians; (void)new_evaluation_point; // Validate callback and user_data_ to prevent use-after-free if (callback_ && user_data_) { // Note: We don't have num_residuals and parameter_block_sizes here // This is a limitation - we'll call with dummy values // In practice, the callback should be set up with proper context callback_(user_data_, 0, 0, nullptr); } } private: ceres_evaluation_callback_t callback_; void* user_data_; }; ceres_problem_options_t* ceres_wrapper_create_problem_options() { return new ceres_problem_options_t; } void ceres_wrapper_free_problem_options(ceres_problem_options_t* options) { // Remove callback from map if it exists (thread-safe) { std::lock_guard lock(evaluation_callbacks_mutex); evaluation_callbacks.erase(options); } delete options; } void ceres_wrapper_problem_options_set_cost_function_ownership( ceres_problem_options_t* options, int ownership) { if (options) { options->options.cost_function_ownership = static_cast(ownership); } } int ceres_wrapper_problem_options_get_cost_function_ownership( const ceres_problem_options_t* options) { return options ? static_cast(options->options.cost_function_ownership) : 0; } void ceres_wrapper_problem_options_set_loss_function_ownership( ceres_problem_options_t* options, int ownership) { if (options) { options->options.loss_function_ownership = static_cast(ownership); } } int ceres_wrapper_problem_options_get_loss_function_ownership( const ceres_problem_options_t* options) { return options ? static_cast(options->options.loss_function_ownership) : 0; } void ceres_wrapper_problem_options_set_manifold_ownership( ceres_problem_options_t* options, int ownership) { if (options) { options->options.manifold_ownership = static_cast(ownership); } } int ceres_wrapper_problem_options_get_manifold_ownership( const ceres_problem_options_t* options) { return options ? static_cast(options->options.manifold_ownership) : 0; } void ceres_wrapper_problem_options_set_enable_fast_removal( ceres_problem_options_t* options, int enable) { if (options) { options->options.enable_fast_removal = (enable != 0); } } int ceres_wrapper_problem_options_get_enable_fast_removal( const ceres_problem_options_t* options) { return options ? (options->options.enable_fast_removal ? 1 : 0) : 0; } void ceres_wrapper_problem_options_set_disable_all_safety_checks( ceres_problem_options_t* options, int disable) { if (options) { options->options.disable_all_safety_checks = (disable != 0); } } int ceres_wrapper_problem_options_get_disable_all_safety_checks( const ceres_problem_options_t* options) { return options ? (options->options.disable_all_safety_checks ? 1 : 0) : 0; } void ceres_wrapper_problem_options_set_evaluation_callback( ceres_problem_options_t* options, ceres_evaluation_callback_t callback, void* user_data) { if (options) { std::lock_guard lock(evaluation_callbacks_mutex); if (callback) { // Create wrapper and store it in the map (thread-safe) evaluation_callbacks[options] = std::make_unique(callback, user_data); options->options.evaluation_callback = evaluation_callbacks[options].get(); } else { evaluation_callbacks.erase(options); options->options.evaluation_callback = nullptr; } } } ceres_problem_t* ceres_wrapper_create_problem_with_options( const ceres_problem_options_t* options) { if (!options) { return reinterpret_cast(new ceres::Problem); } return reinterpret_cast(new ceres::Problem(options->options)); } // ============================================================================ // Parameter Bounds (Phase 2) // ============================================================================ void ceres_wrapper_problem_set_parameter_lower_bound( ceres_problem_t* problem, double* parameters, int index, double lower_bound) { if (problem) { reinterpret_cast(problem)->SetParameterLowerBound(parameters, index, lower_bound); } } void ceres_wrapper_problem_set_parameter_upper_bound( ceres_problem_t* problem, double* parameters, int index, double upper_bound) { if (problem) { reinterpret_cast(problem)->SetParameterUpperBound(parameters, index, upper_bound); } } double ceres_wrapper_problem_get_parameter_lower_bound( const ceres_problem_t* problem, const double* parameters, int index) { if (!problem) { return -std::numeric_limits::infinity(); } return reinterpret_cast(problem)->GetParameterLowerBound(parameters, index); } double ceres_wrapper_problem_get_parameter_upper_bound( const ceres_problem_t* problem, const double* parameters, int index) { if (!problem) { return std::numeric_limits::infinity(); } return reinterpret_cast(problem)->GetParameterUpperBound(parameters, index); } // ============================================================================ // Additional SolverSummary Fields (Phase 2) // ============================================================================ // Note: Timing functions are already defined above (lines 355-378) // Only add new functions that don't exist yet int ceres_wrapper_solver_summary_get_num_parameter_blocks( const ceres_solver_summary_t* summary) { return summary ? summary->summary.num_parameter_blocks : 0; } int ceres_wrapper_solver_summary_get_num_parameters( const ceres_solver_summary_t* summary) { return summary ? summary->summary.num_parameters : 0; } int ceres_wrapper_solver_summary_get_num_effective_parameters( const ceres_solver_summary_t* summary) { return summary ? summary->summary.num_effective_parameters : 0; } int ceres_wrapper_solver_summary_get_num_residual_blocks( const ceres_solver_summary_t* summary) { return summary ? summary->summary.num_residual_blocks : 0; } int ceres_wrapper_solver_summary_get_num_residuals( const ceres_solver_summary_t* summary) { return summary ? summary->summary.num_residuals : 0; } double ceres_wrapper_solver_summary_get_cost_change( const ceres_solver_summary_t* summary) { if (!summary) { return 0.0; } return summary->summary.initial_cost - summary->summary.final_cost; } // ============================================================================ // Covariance Estimation (Phase 3 - HIGH Priority) // ============================================================================ struct ceres_covariance_options_s { ceres::Covariance::Options options; }; ceres_covariance_options_t* ceres_wrapper_create_covariance_options() { return new ceres_covariance_options_t; } void ceres_wrapper_free_covariance_options(ceres_covariance_options_t* options) { delete options; } void ceres_wrapper_covariance_options_set_num_threads( ceres_covariance_options_t* options, int num_threads) { if (options) { options->options.num_threads = num_threads; } } int ceres_wrapper_covariance_options_get_num_threads( const ceres_covariance_options_t* options) { return options ? options->options.num_threads : 0; } void ceres_wrapper_covariance_options_set_sparse_linear_algebra_library_type( ceres_covariance_options_t* options, int library_type) { if (options) { options->options.sparse_linear_algebra_library_type = static_cast(library_type); } } int ceres_wrapper_covariance_options_get_sparse_linear_algebra_library_type( const ceres_covariance_options_t* options) { return options ? static_cast(options->options.sparse_linear_algebra_library_type) : 0; } void ceres_wrapper_covariance_options_set_algorithm_type( ceres_covariance_options_t* options, int algorithm_type) { if (options) { options->options.algorithm_type = static_cast(algorithm_type); } } int ceres_wrapper_covariance_options_get_algorithm_type( const ceres_covariance_options_t* options) { return options ? static_cast(options->options.algorithm_type) : 0; } void ceres_wrapper_covariance_options_set_min_reciprocal_condition_number( ceres_covariance_options_t* options, double min_reciprocal_condition_number) { if (options) { options->options.min_reciprocal_condition_number = min_reciprocal_condition_number; } } double ceres_wrapper_covariance_options_get_min_reciprocal_condition_number( const ceres_covariance_options_t* options) { return options ? options->options.min_reciprocal_condition_number : 0.0; } void ceres_wrapper_covariance_options_set_null_space_rank( ceres_covariance_options_t* options, int null_space_rank) { if (options) { options->options.null_space_rank = null_space_rank; } } int ceres_wrapper_covariance_options_get_null_space_rank( const ceres_covariance_options_t* options) { return options ? options->options.null_space_rank : 0; } void ceres_wrapper_covariance_options_set_apply_loss_function( ceres_covariance_options_t* options, int apply_loss_function) { if (options) { options->options.apply_loss_function = (apply_loss_function != 0); } } int ceres_wrapper_covariance_options_get_apply_loss_function( const ceres_covariance_options_t* options) { return options ? (options->options.apply_loss_function ? 1 : 0) : 0; } struct ceres_covariance_s { std::unique_ptr covariance; // Constructor with default options ceres_covariance_s() : covariance(std::make_unique(ceres::Covariance::Options())) {} // Constructor with custom options ceres_covariance_s(const ceres::Covariance::Options& options) : covariance(std::make_unique(options)) {} }; ceres_covariance_t* ceres_wrapper_create_covariance() { return new ceres_covariance_t; } // Create covariance with options ceres_covariance_t* ceres_wrapper_create_covariance_with_options( const ceres_covariance_options_t* options) { if (!options) { return new ceres_covariance_t; } return new ceres_covariance_t(options->options); } void ceres_wrapper_free_covariance(ceres_covariance_t* covariance) { delete covariance; } ceres_wrapper_error_code_t ceres_wrapper_covariance_compute( ceres_covariance_t* covariance, const ceres_problem_t* problem, const ceres_covariance_options_t* options, const double** parameter_blocks, int num_parameter_blocks, char* error_message, int error_message_size) { if (!covariance) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "covariance is null", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_NULL_POINTER; } if (!problem) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "problem is null", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_NULL_POINTER; } if (!parameter_blocks) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "parameter_blocks is null", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_NULL_POINTER; } if (num_parameter_blocks <= 0) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "num_parameter_blocks must be positive", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_INVALID_PARAMETER; } try { // Covariance::Compute requires non-const Problem*, so we need to cast ceres::Problem* ceres_problem = const_cast( reinterpret_cast(problem)); std::vector param_blocks(parameter_blocks, parameter_blocks + num_parameter_blocks); // Note: Options are set when creating covariance object // If different options are needed, user should create a new covariance object (void)options; // Options parameter kept for API compatibility but not used here bool success = covariance->covariance->Compute(param_blocks, ceres_problem); if (!success) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "Covariance computation failed", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_INVALID_OPERATION; } return CERES_WRAPPER_SUCCESS; } catch (const std::exception& e) { if (error_message && error_message_size > 0) { std::strncpy(error_message, e.what(), error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_EXCEPTION; } catch (...) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "Unknown exception during covariance computation", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_EXCEPTION; } } ceres_wrapper_error_code_t ceres_wrapper_covariance_get_covariance_block( const ceres_covariance_t* covariance, const double* parameter_block1, const double* parameter_block2, double* covariance_block, char* error_message, int error_message_size) { if (!covariance) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "covariance is null", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_NULL_POINTER; } if (!parameter_block1) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "parameter_block1 is null", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_NULL_POINTER; } if (!parameter_block2) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "parameter_block2 is null", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_NULL_POINTER; } if (!covariance_block) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "covariance_block is null", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_NULL_POINTER; } try { // GetCovarianceBlock requires the covariance_block array to be pre-allocated // with size = block1_size * block2_size // Caller is responsible for providing correctly sized array bool success = covariance->covariance->GetCovarianceBlock( parameter_block1, parameter_block2, covariance_block); if (!success) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "Failed to get covariance block", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_NOT_FOUND; } return CERES_WRAPPER_SUCCESS; } catch (const std::exception& e) { if (error_message && error_message_size > 0) { std::strncpy(error_message, e.what(), error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_EXCEPTION; } catch (...) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "Unknown exception", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_EXCEPTION; } } ceres_wrapper_error_code_t ceres_wrapper_covariance_get_covariance_matrix( const ceres_covariance_t* covariance, const double** parameter_blocks, int num_parameter_blocks, double* covariance_matrix, char* error_message, int error_message_size) { if (!covariance) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "covariance is null", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_NULL_POINTER; } if (!parameter_blocks) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "parameter_blocks is null", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_NULL_POINTER; } if (!covariance_matrix) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "covariance_matrix is null", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_NULL_POINTER; } if (num_parameter_blocks <= 0) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "num_parameter_blocks must be positive", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_INVALID_PARAMETER; } try { std::vector param_blocks(parameter_blocks, parameter_blocks + num_parameter_blocks); bool success = covariance->covariance->GetCovarianceMatrix( param_blocks, covariance_matrix); if (!success) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "Failed to get covariance matrix", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_INVALID_OPERATION; } return CERES_WRAPPER_SUCCESS; } catch (const std::exception& e) { if (error_message && error_message_size > 0) { std::strncpy(error_message, e.what(), error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_EXCEPTION; } catch (...) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "Unknown exception", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_EXCEPTION; } } // ============================================================================ // Gradient Checker (Phase 3 - MEDIUM Priority) // ============================================================================ struct ceres_gradient_checker_options_s { ceres::NumericDiffOptions numeric_diff_options; double gradient_check_relative_precision; // Stored separately, used in Probe() }; ceres_gradient_checker_options_t* ceres_wrapper_create_gradient_checker_options() { auto* opts = new ceres_gradient_checker_options_s; opts->gradient_check_relative_precision = 1e-4; // Default value return opts; } void ceres_wrapper_free_gradient_checker_options(ceres_gradient_checker_options_t* options) { delete options; } void ceres_wrapper_gradient_checker_options_set_gradient_check_relative_precision( ceres_gradient_checker_options_t* options, double precision) { if (options) { options->gradient_check_relative_precision = precision; } } double ceres_wrapper_gradient_checker_options_get_gradient_check_relative_precision( const ceres_gradient_checker_options_t* options) { if (!options) return 0.0; return options->gradient_check_relative_precision; } void ceres_wrapper_gradient_checker_options_set_gradient_check_numeric_derivative_relative_step_size( ceres_gradient_checker_options_t* options, double step_size) { if (options) { options->numeric_diff_options.relative_step_size = step_size; } } double ceres_wrapper_gradient_checker_options_get_gradient_check_numeric_derivative_relative_step_size( const ceres_gradient_checker_options_t* options) { if (!options) return 0.0; return options->numeric_diff_options.relative_step_size; } struct ceres_gradient_checker_s { std::unique_ptr checker; }; ceres_gradient_checker_t* ceres_wrapper_create_gradient_checker( void* cost_function, const ceres_manifold_t** manifolds, int num_manifolds, const ceres_gradient_checker_options_t* options) { if (!cost_function) { return nullptr; } try { ceres::CostFunction* ceres_cost_function = reinterpret_cast(cost_function); std::vector ceres_manifolds; if (manifolds && num_manifolds > 0) { for (int i = 0; i < num_manifolds; ++i) { if (manifolds[i]) { ceres_manifolds.push_back(manifolds[i]->manifold.get()); } } } ceres::NumericDiffOptions numeric_diff_options; if (options) { numeric_diff_options = options->numeric_diff_options; } // GradientChecker constructor takes pointer to vector or nullptr const std::vector* manifolds_ptr = ceres_manifolds.empty() ? nullptr : &ceres_manifolds; auto* checker = new ceres_gradient_checker_s; checker->checker = std::make_unique( ceres_cost_function, manifolds_ptr, numeric_diff_options); return checker; } catch (...) { return nullptr; } } void ceres_wrapper_free_gradient_checker(ceres_gradient_checker_t* checker) { delete checker; } ceres_wrapper_error_code_t ceres_wrapper_gradient_checker_probe( const ceres_gradient_checker_t* checker, const double* const* parameters, double relative_precision, char* error_message, int error_message_size) { if (!checker) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "checker is null", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_NULL_POINTER; } if (!parameters) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "parameters is null", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_NULL_POINTER; } try { ceres::GradientChecker::ProbeResults results; bool success = checker->checker->Probe(parameters, relative_precision, &results); if (error_message && error_message_size > 0) { if (!results.error_log.empty()) { int copy_size = std::min(static_cast(results.error_log.length()), error_message_size - 1); std::strncpy(error_message, results.error_log.c_str(), copy_size); error_message[copy_size] = '\0'; } else if (success) { error_message[0] = '\0'; } else { std::strncpy(error_message, "Gradient check failed", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } } if (!success) { return CERES_WRAPPER_ERROR_INVALID_PARAMETER; // Gradients don't match } return CERES_WRAPPER_SUCCESS; } catch (const std::exception& e) { if (error_message && error_message_size > 0) { std::strncpy(error_message, e.what(), error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_EXCEPTION; } catch (...) { if (error_message && error_message_size > 0) { std::strncpy(error_message, "Unknown exception during gradient check", error_message_size - 1); error_message[error_message_size - 1] = '\0'; } return CERES_WRAPPER_ERROR_EXCEPTION; } } // ============================================================================ // Context (Phase 3 - MEDIUM Priority) // ============================================================================ struct ceres_context_s { ceres::Context* context; ceres_context_s() : context(ceres::Context::Create()) {} ~ceres_context_s() { delete context; } }; ceres_context_t* ceres_wrapper_create_context() { return new ceres_context_s; } void ceres_wrapper_free_context(ceres_context_t* context) { delete context; } void ceres_wrapper_problem_options_set_context( ceres_problem_options_t* options, ceres_context_t* context) { if (options && context) { // Context::Create() returns ContextImpl* which is the correct type options->options.context = context->context; } } // ============================================================================ // Cubic Interpolator (1D) (Phase 3 - MEDIUM Priority) // ============================================================================ // Adapter for 1D array data to work with Ceres CubicInterpolator class Array1DAdapter { public: enum { DATA_DIMENSION = 1 }; Array1DAdapter(const double* data, int size) : data_(data), size_(size) {} void GetValue(const int index, double* const value) const { if (index < 0 || index >= size_) { *value = 0.0; // Out of bounds - return default value } else { *value = data_[index]; } } int NumValues() const { return size_; } private: const double* data_; int size_; }; struct ceres_cubic_interpolator_s { std::unique_ptr> interpolator; std::unique_ptr adapter; std::vector data; // Keep data alive }; ceres_cubic_interpolator_t* ceres_wrapper_create_cubic_interpolator( const double* data, int num_values) { if (!data || num_values <= 0) { return nullptr; } auto* interp = new ceres_cubic_interpolator_t; // Copy data to keep it alive interp->data.assign(data, data + num_values); // Create adapter with copied data interp->adapter = std::make_unique( interp->data.data(), num_values); // Create interpolator with adapter interp->interpolator = std::make_unique>( *interp->adapter); return interp; } void ceres_wrapper_free_cubic_interpolator( ceres_cubic_interpolator_t* interpolator) { delete interpolator; } void ceres_wrapper_cubic_interpolator_evaluate( const ceres_cubic_interpolator_t* interpolator, double x, double* value, double* gradient) { if (!interpolator) { if (value) *value = 0.0; if (gradient) *gradient = 0.0; return; } double val = 0.0; double grad = 0.0; // Evaluate at index x (clamped to valid range) double clamped_x = std::max(0.0, std::min(x, static_cast(interpolator->adapter->NumValues() - 1))); interpolator->interpolator->Evaluate(clamped_x, &val, &grad); if (value) *value = val; if (gradient) *gradient = grad; }