Initial commit

This commit is contained in:
2026-07-13 09:25:40 +07:00
parent c08ff54676
commit bccfb156d7
1938 changed files with 641646 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
using System;
using CeresSharp.Native;
using CeresSharp.Native.SafeHandles;
namespace CeresSharp.Advanced;
/// <summary>
/// Context for performance optimization.
/// Allows reuse of expensive objects across multiple solves.
/// </summary>
public sealed class Context : IDisposable
{
private readonly ContextHandle _handle;
private bool _disposed;
/// <summary>
/// Creates a new Context instance.
/// </summary>
public Context()
{
_handle = ContextHandle.Create();
}
/// <summary>
/// Gets the native handle.
/// </summary>
internal ContextHandle Handle => _handle;
public void Dispose()
{
if (!_disposed)
{
_handle?.Dispose();
_disposed = true;
}
}
}

View File

@@ -0,0 +1,211 @@
using System;
using CeresSharp;
using CeresSharp.Native;
using CeresSharp.Native.SafeHandles;
namespace CeresSharp.Advanced;
/// <summary>
/// Covariance estimation for parameter blocks.
/// </summary>
public sealed class Covariance : IDisposable
{
private readonly CovarianceHandle _handle;
private bool _disposed;
/// <summary>
/// Creates a new Covariance instance with default options.
/// </summary>
public Covariance()
{
_handle = CovarianceHandle.Create();
}
/// <summary>
/// Creates a new Covariance instance with the specified options.
/// </summary>
public Covariance(CovarianceOptions options)
{
if (options == null)
throw new ArgumentNullException(nameof(options));
_handle = CovarianceHandle.CreateWithOptions(options.Handle.DangerousGetHandle());
}
/// <summary>
/// Gets the native handle.
/// </summary>
internal CovarianceHandle Handle => _handle;
/// <summary>
/// Computes the covariance for the specified parameter blocks.
/// </summary>
/// <param name="problem">The problem.</param>
/// <param name="options">The covariance options.</param>
/// <param name="parameterBlocks">Array of parameter block arrays.</param>
/// <exception cref="Exceptions.CeresException">Thrown if the operation fails.</exception>
public void Compute(Problem problem, CovarianceOptions options, double[][] parameterBlocks)
{
if (problem == null)
throw new ArgumentNullException(nameof(problem));
if (options == null)
throw new ArgumentNullException(nameof(options));
if (parameterBlocks == null || parameterBlocks.Length == 0)
throw new ArgumentException("Parameter blocks cannot be null or empty", nameof(parameterBlocks));
unsafe
{
var parameterBlockPtrs = new IntPtr[parameterBlocks.Length];
var pinnedArrays = new System.Runtime.InteropServices.GCHandle[parameterBlocks.Length];
try
{
for (int i = 0; i < parameterBlocks.Length; i++)
{
if (parameterBlocks[i] == null)
throw new ArgumentException($"Parameter block {i} is null", nameof(parameterBlocks));
var handle = System.Runtime.InteropServices.GCHandle.Alloc(parameterBlocks[i], System.Runtime.InteropServices.GCHandleType.Pinned);
pinnedArrays[i] = handle;
parameterBlockPtrs[i] = handle.AddrOfPinnedObject();
}
var errorMessage = new System.Text.StringBuilder(512);
fixed (IntPtr* ptrs = parameterBlockPtrs)
{
var errorCode = CeresNative.ceres_wrapper_covariance_compute(
_handle.DangerousGetHandle(),
problem.Handle.DangerousGetHandle(),
options.Handle.DangerousGetHandle(),
(IntPtr)ptrs,
parameterBlocks.Length,
errorMessage,
errorMessage.Capacity);
if (errorCode != Exceptions.CeresErrorCode.Success)
{
var message = errorMessage.Length > 0 ? errorMessage.ToString() : null;
throw new Exceptions.CeresException(errorCode, message);
}
}
}
finally
{
foreach (var handle in pinnedArrays)
{
if (handle.IsAllocated)
handle.Free();
}
}
}
}
/// <summary>
/// Gets the covariance block between two parameter blocks.
/// </summary>
/// <param name="parameterBlock1">First parameter block.</param>
/// <param name="parameterBlock2">Second parameter block.</param>
/// <param name="covarianceBlock">Output covariance block matrix (row-major).</param>
/// <exception cref="Exceptions.CeresException">Thrown if the operation fails.</exception>
public void GetCovarianceBlock(double[] parameterBlock1, double[] parameterBlock2, double[] covarianceBlock)
{
if (parameterBlock1 == null)
throw new ArgumentNullException(nameof(parameterBlock1));
if (parameterBlock2 == null)
throw new ArgumentNullException(nameof(parameterBlock2));
if (covarianceBlock == null)
throw new ArgumentNullException(nameof(covarianceBlock));
unsafe
{
var errorMessage = new System.Text.StringBuilder(512);
fixed (double* ptr1 = parameterBlock1)
fixed (double* ptr2 = parameterBlock2)
fixed (double* covPtr = covarianceBlock)
{
var errorCode = CeresNative.ceres_wrapper_covariance_get_covariance_block(
_handle.DangerousGetHandle(),
(IntPtr)ptr1,
(IntPtr)ptr2,
(IntPtr)covPtr,
errorMessage,
errorMessage.Capacity);
if (errorCode != Exceptions.CeresErrorCode.Success)
{
var message = errorMessage.Length > 0 ? errorMessage.ToString() : null;
throw new Exceptions.CeresException(errorCode, message);
}
}
}
}
/// <summary>
/// Gets the covariance matrix for multiple parameter blocks.
/// </summary>
/// <param name="parameterBlocks">Array of parameter block arrays.</param>
/// <param name="covarianceMatrix">Output covariance matrix (row-major).</param>
/// <exception cref="Exceptions.CeresException">Thrown if the operation fails.</exception>
public void GetCovarianceMatrix(double[][] parameterBlocks, double[] covarianceMatrix)
{
if (parameterBlocks == null || parameterBlocks.Length == 0)
throw new ArgumentException("Parameter blocks cannot be null or empty", nameof(parameterBlocks));
if (covarianceMatrix == null)
throw new ArgumentNullException(nameof(covarianceMatrix));
unsafe
{
var parameterBlockPtrs = new IntPtr[parameterBlocks.Length];
var pinnedArrays = new System.Runtime.InteropServices.GCHandle[parameterBlocks.Length];
try
{
for (int i = 0; i < parameterBlocks.Length; i++)
{
if (parameterBlocks[i] == null)
throw new ArgumentException($"Parameter block {i} is null", nameof(parameterBlocks));
var handle = System.Runtime.InteropServices.GCHandle.Alloc(parameterBlocks[i], System.Runtime.InteropServices.GCHandleType.Pinned);
pinnedArrays[i] = handle;
parameterBlockPtrs[i] = handle.AddrOfPinnedObject();
}
var errorMessage = new System.Text.StringBuilder(512);
fixed (IntPtr* ptrs = parameterBlockPtrs)
fixed (double* covPtr = covarianceMatrix)
{
var errorCode = CeresNative.ceres_wrapper_covariance_get_covariance_matrix(
_handle.DangerousGetHandle(),
(IntPtr)ptrs,
parameterBlocks.Length,
(IntPtr)covPtr,
errorMessage,
errorMessage.Capacity);
if (errorCode != Exceptions.CeresErrorCode.Success)
{
var message = errorMessage.Length > 0 ? errorMessage.ToString() : null;
throw new Exceptions.CeresException(errorCode, message);
}
}
}
finally
{
foreach (var handle in pinnedArrays)
{
if (handle.IsAllocated)
handle.Free();
}
}
}
}
public void Dispose()
{
if (!_disposed)
{
_handle?.Dispose();
_disposed = true;
}
}
}

View File

@@ -0,0 +1,91 @@
using System;
using CeresSharp.Enums;
using CeresSharp.Native;
using CeresSharp.Native.SafeHandles;
namespace CeresSharp.Advanced;
/// <summary>
/// Options for covariance estimation.
/// </summary>
public sealed class CovarianceOptions : IDisposable
{
private readonly CovarianceOptionsHandle _handle;
private bool _disposed;
/// <summary>
/// Creates a new CovarianceOptions instance with default settings.
/// </summary>
public CovarianceOptions()
{
_handle = CovarianceOptionsHandle.Create();
}
/// <summary>
/// Gets the native handle.
/// </summary>
internal CovarianceOptionsHandle Handle => _handle;
/// <summary>
/// Gets or sets the number of threads to use.
/// </summary>
public int NumThreads
{
get => CeresNative.ceres_wrapper_covariance_options_get_num_threads(_handle.DangerousGetHandle());
set => CeresNative.ceres_wrapper_covariance_options_set_num_threads(_handle.DangerousGetHandle(), value);
}
/// <summary>
/// Gets or sets the sparse linear algebra library type.
/// </summary>
public SparseLinearAlgebraLibraryType SparseLinearAlgebraLibraryType
{
get => (SparseLinearAlgebraLibraryType)CeresNative.ceres_wrapper_covariance_options_get_sparse_linear_algebra_library_type(_handle.DangerousGetHandle());
set => CeresNative.ceres_wrapper_covariance_options_set_sparse_linear_algebra_library_type(_handle.DangerousGetHandle(), (int)value);
}
/// <summary>
/// Gets or sets the algorithm type.
/// </summary>
public CovarianceAlgorithmType AlgorithmType
{
get => (CovarianceAlgorithmType)CeresNative.ceres_wrapper_covariance_options_get_algorithm_type(_handle.DangerousGetHandle());
set => CeresNative.ceres_wrapper_covariance_options_set_algorithm_type(_handle.DangerousGetHandle(), (int)value);
}
/// <summary>
/// Gets or sets the minimum reciprocal condition number.
/// </summary>
public double MinReciprocalConditionNumber
{
get => CeresNative.ceres_wrapper_covariance_options_get_min_reciprocal_condition_number(_handle.DangerousGetHandle());
set => CeresNative.ceres_wrapper_covariance_options_set_min_reciprocal_condition_number(_handle.DangerousGetHandle(), value);
}
/// <summary>
/// Gets or sets the null space rank.
/// </summary>
public int NullSpaceRank
{
get => CeresNative.ceres_wrapper_covariance_options_get_null_space_rank(_handle.DangerousGetHandle());
set => CeresNative.ceres_wrapper_covariance_options_set_null_space_rank(_handle.DangerousGetHandle(), value);
}
/// <summary>
/// Gets or sets whether to apply loss function.
/// </summary>
public bool ApplyLossFunction
{
get => CeresNative.ceres_wrapper_covariance_options_get_apply_loss_function(_handle.DangerousGetHandle()) != 0;
set => CeresNative.ceres_wrapper_covariance_options_set_apply_loss_function(_handle.DangerousGetHandle(), value ? 1 : 0);
}
public void Dispose()
{
if (!_disposed)
{
_handle?.Dispose();
_disposed = true;
}
}
}

View File

@@ -0,0 +1,10 @@
namespace CeresSharp.Advanced;
/// <summary>
/// Delegate for evaluation callback.
/// Called before evaluating cost functions to allow shared computation.
/// </summary>
/// <param name="numResiduals">Number of residuals.</param>
/// <param name="numParameterBlocks">Number of parameter blocks.</param>
/// <param name="parameterBlockSizes">Array of sizes for each parameter block (can be null).</param>
public delegate void EvaluationCallback(int numResiduals, int numParameterBlocks, int[]? parameterBlockSizes);

View File

@@ -0,0 +1,211 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
using CeresSharp.Native;
using CeresSharp.Native.SafeHandles;
using CeresSharp;
namespace CeresSharp.Advanced;
/// <summary>
/// Gradient checker for validating cost function gradients.
/// </summary>
public sealed class GradientChecker : IDisposable
{
private readonly GradientCheckerHandle _handle;
private bool _disposed;
/// <summary>
/// Creates a new GradientChecker instance.
/// </summary>
/// <param name="costFunction">The cost function to check.</param>
/// <param name="manifolds">Array of manifolds (can be null if no manifolds).</param>
/// <param name="options">The gradient checker options.</param>
public GradientChecker(CostFunction costFunction, Manifold[]? manifolds, GradientCheckerOptions options)
{
if (costFunction == null)
throw new ArgumentNullException(nameof(costFunction));
if (options == null)
throw new ArgumentNullException(nameof(options));
unsafe
{
IntPtr manifoldsPtr = IntPtr.Zero;
int numManifolds = 0;
if (manifolds != null && manifolds.Length > 0)
{
var manifoldHandles = new IntPtr[manifolds.Length];
for (int i = 0; i < manifolds.Length; i++)
{
if (manifolds[i] == null)
throw new ArgumentException($"Manifold {i} is null", nameof(manifolds));
manifoldHandles[i] = manifolds[i].Handle;
}
fixed (IntPtr* ptr = manifoldHandles)
{
manifoldsPtr = (IntPtr)ptr;
numManifolds = manifolds.Length;
}
}
var handle = CeresNative.ceres_wrapper_create_gradient_checker(
costFunction.Handle,
manifoldsPtr,
numManifolds,
options.Handle.DangerousGetHandle());
_handle = GradientCheckerHandle.Create(handle);
}
}
/// <summary>
/// Gets the native handle.
/// </summary>
internal GradientCheckerHandle Handle => _handle;
/// <summary>
/// Probes gradients at the given parameters.
/// </summary>
/// <param name="parameters">Array of parameter block arrays.</param>
/// <param name="relativePrecision">Relative precision for comparison.</param>
/// <returns>True if gradients match, false otherwise.</returns>
/// <exception cref="Exceptions.CeresException">Thrown if the operation fails (other than gradient mismatch).</exception>
public bool Probe(double[][] parameters, double relativePrecision)
{
if (parameters == null || parameters.Length == 0)
throw new ArgumentException("Parameters cannot be null or empty", nameof(parameters));
unsafe
{
var parameterBlockPtrs = new IntPtr[parameters.Length];
var pinnedArrays = new GCHandle[parameters.Length];
try
{
for (int i = 0; i < parameters.Length; i++)
{
if (parameters[i] == null)
throw new ArgumentException($"Parameter block {i} is null", nameof(parameters));
var handle = GCHandle.Alloc(parameters[i], GCHandleType.Pinned);
pinnedArrays[i] = handle;
parameterBlockPtrs[i] = handle.AddrOfPinnedObject();
}
var sb = new StringBuilder(512);
fixed (IntPtr* ptrs = parameterBlockPtrs)
{
var errorCode = CeresNative.ceres_wrapper_gradient_checker_probe(
_handle.DangerousGetHandle(),
(IntPtr)ptrs,
relativePrecision,
sb,
sb.Capacity);
// InvalidParameter means gradients don't match (expected behavior)
if (errorCode == Exceptions.CeresErrorCode.InvalidParameter)
{
return false;
}
// Other error codes indicate actual errors
if (errorCode != Exceptions.CeresErrorCode.Success)
{
var message = sb.Length > 0 ? sb.ToString() : null;
throw new Exceptions.CeresException(errorCode, message);
}
return true;
}
}
finally
{
foreach (var handle in pinnedArrays)
{
if (handle.IsAllocated)
handle.Free();
}
}
}
}
/// <summary>
/// Probes gradients at the given parameters and returns error message if they don't match.
/// </summary>
/// <param name="parameters">Array of parameter block arrays.</param>
/// <param name="relativePrecision">Relative precision for comparison.</param>
/// <param name="errorMessage">Output error message if gradients don't match.</param>
/// <returns>True if gradients match, false otherwise.</returns>
/// <exception cref="Exceptions.CeresException">Thrown if the operation fails (other than gradient mismatch).</exception>
public bool Probe(double[][] parameters, double relativePrecision, out string? errorMessage)
{
if (parameters == null || parameters.Length == 0)
throw new ArgumentException("Parameters cannot be null or empty", nameof(parameters));
unsafe
{
var parameterBlockPtrs = new IntPtr[parameters.Length];
var pinnedArrays = new GCHandle[parameters.Length];
try
{
for (int i = 0; i < parameters.Length; i++)
{
if (parameters[i] == null)
throw new ArgumentException($"Parameter block {i} is null", nameof(parameters));
var handle = GCHandle.Alloc(parameters[i], GCHandleType.Pinned);
pinnedArrays[i] = handle;
parameterBlockPtrs[i] = handle.AddrOfPinnedObject();
}
var sb = new StringBuilder(512);
fixed (IntPtr* ptrs = parameterBlockPtrs)
{
var errorCode = CeresNative.ceres_wrapper_gradient_checker_probe(
_handle.DangerousGetHandle(),
(IntPtr)ptrs,
relativePrecision,
sb,
sb.Capacity);
// InvalidParameter means gradients don't match (expected behavior)
if (errorCode == Exceptions.CeresErrorCode.InvalidParameter)
{
errorMessage = sb.Length > 0 ? sb.ToString() : "Gradient check failed";
return false;
}
// Other error codes indicate actual errors
if (errorCode != Exceptions.CeresErrorCode.Success)
{
var message = sb.Length > 0 ? sb.ToString() : null;
throw new Exceptions.CeresException(errorCode, message);
}
errorMessage = null;
return true;
}
}
finally
{
foreach (var handle in pinnedArrays)
{
if (handle.IsAllocated)
handle.Free();
}
}
}
}
public void Dispose()
{
if (!_disposed)
{
_handle?.Dispose();
_disposed = true;
}
}
}

View File

@@ -0,0 +1,54 @@
using System;
using CeresSharp.Native;
using CeresSharp.Native.SafeHandles;
namespace CeresSharp.Advanced;
/// <summary>
/// Options for gradient checking.
/// </summary>
public sealed class GradientCheckerOptions : IDisposable
{
private readonly GradientCheckerOptionsHandle _handle;
private bool _disposed;
/// <summary>
/// Creates a new GradientCheckerOptions instance with default settings.
/// </summary>
public GradientCheckerOptions()
{
_handle = GradientCheckerOptionsHandle.Create();
}
/// <summary>
/// Gets the native handle.
/// </summary>
internal GradientCheckerOptionsHandle Handle => _handle;
/// <summary>
/// Gets or sets the gradient check relative precision.
/// </summary>
public double GradientCheckRelativePrecision
{
get => CeresNative.ceres_wrapper_gradient_checker_options_get_gradient_check_relative_precision(_handle.DangerousGetHandle());
set => CeresNative.ceres_wrapper_gradient_checker_options_set_gradient_check_relative_precision(_handle.DangerousGetHandle(), value);
}
/// <summary>
/// Gets or sets the numeric derivative relative step size.
/// </summary>
public double NumericDerivativeRelativeStepSize
{
get => CeresNative.ceres_wrapper_gradient_checker_options_get_gradient_check_numeric_derivative_relative_step_size(_handle.DangerousGetHandle());
set => CeresNative.ceres_wrapper_gradient_checker_options_set_gradient_check_numeric_derivative_relative_step_size(_handle.DangerousGetHandle(), value);
}
public void Dispose()
{
if (!_disposed)
{
_handle?.Dispose();
_disposed = true;
}
}
}

View File

@@ -0,0 +1,153 @@
using System;
using CeresSharp.Native;
using CeresSharp.Native.SafeHandles;
namespace CeresSharp.Advanced;
/// <summary>
/// Options for configuring a Ceres Problem.
/// </summary>
public sealed class ProblemOptions : IDisposable
{
private readonly ProblemOptionsHandle _handle;
private System.Runtime.InteropServices.GCHandle? _evaluationCallbackHandle;
private System.Runtime.InteropServices.GCHandle? _evaluationNativeCallbackHandle;
private bool _disposed;
/// <summary>
/// Creates a new ProblemOptions instance with default settings.
/// </summary>
public ProblemOptions()
{
_handle = ProblemOptionsHandle.Create();
}
/// <summary>
/// Gets the native handle.
/// </summary>
internal ProblemOptionsHandle Handle => _handle;
/// <summary>
/// Gets or sets whether the problem owns cost functions.
/// </summary>
public bool CostFunctionOwnership
{
get => CeresNative.ceres_wrapper_problem_options_get_cost_function_ownership(_handle.DangerousGetHandle()) != 0;
set => CeresNative.ceres_wrapper_problem_options_set_cost_function_ownership(_handle.DangerousGetHandle(), value ? 1 : 0);
}
/// <summary>
/// Gets or sets whether the problem owns loss functions.
/// </summary>
public bool LossFunctionOwnership
{
get => CeresNative.ceres_wrapper_problem_options_get_loss_function_ownership(_handle.DangerousGetHandle()) != 0;
set => CeresNative.ceres_wrapper_problem_options_set_loss_function_ownership(_handle.DangerousGetHandle(), value ? 1 : 0);
}
/// <summary>
/// Gets or sets whether the problem owns manifolds.
/// </summary>
public bool ManifoldOwnership
{
get => CeresNative.ceres_wrapper_problem_options_get_manifold_ownership(_handle.DangerousGetHandle()) != 0;
set => CeresNative.ceres_wrapper_problem_options_set_manifold_ownership(_handle.DangerousGetHandle(), value ? 1 : 0);
}
/// <summary>
/// Gets or sets whether to enable fast removal of residual blocks.
/// </summary>
public bool EnableFastRemoval
{
get => CeresNative.ceres_wrapper_problem_options_get_enable_fast_removal(_handle.DangerousGetHandle()) != 0;
set => CeresNative.ceres_wrapper_problem_options_set_enable_fast_removal(_handle.DangerousGetHandle(), value ? 1 : 0);
}
/// <summary>
/// Gets or sets whether to disable all safety checks.
/// </summary>
public bool DisableAllSafetyChecks
{
get => CeresNative.ceres_wrapper_problem_options_get_disable_all_safety_checks(_handle.DangerousGetHandle()) != 0;
set => CeresNative.ceres_wrapper_problem_options_set_disable_all_safety_checks(_handle.DangerousGetHandle(), value ? 1 : 0);
}
/// <summary>
/// Sets the context for performance optimization.
/// </summary>
public void SetContext(Context context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
CeresNative.ceres_wrapper_problem_options_set_context(
_handle.DangerousGetHandle(),
context.Handle.DangerousGetHandle());
}
/// <summary>
/// Sets an evaluation callback for shared computation.
/// Called before evaluating cost functions to allow shared computation.
/// </summary>
/// <param name="callback">The evaluation callback.</param>
public void SetEvaluationCallback(EvaluationCallback callback)
{
if (callback == null)
throw new ArgumentNullException(nameof(callback));
// Pin the callback delegate
var callbackHandle = System.Runtime.InteropServices.GCHandle.Alloc(callback);
// Create native callback wrapper
var nativeCallback = new CeresNative.CeresEvaluationCallback((userData, numResiduals, numParameterBlocks, parameterBlockSizesPtr) =>
{
try
{
var handle = System.Runtime.InteropServices.GCHandle.FromIntPtr(userData);
var csCallback = (EvaluationCallback)handle.Target!;
// Parse parameter block sizes
int[]? parameterBlockSizes = null;
if (parameterBlockSizesPtr != IntPtr.Zero && numParameterBlocks > 0)
{
parameterBlockSizes = new int[numParameterBlocks];
System.Runtime.InteropServices.Marshal.Copy(parameterBlockSizesPtr, parameterBlockSizes, 0, numParameterBlocks);
}
// Call C# callback
csCallback(numResiduals, numParameterBlocks, parameterBlockSizes);
}
catch
{
// Ignore errors in callback
}
});
// Pin the native callback
var nativeCallbackHandle = System.Runtime.InteropServices.GCHandle.Alloc(nativeCallback);
CeresNative.ceres_wrapper_problem_options_set_evaluation_callback(
_handle.DangerousGetHandle(),
nativeCallback,
System.Runtime.InteropServices.GCHandle.ToIntPtr(callbackHandle));
// Track handles for cleanup when ProblemOptions is disposed
_evaluationCallbackHandle = callbackHandle;
_evaluationNativeCallbackHandle = nativeCallbackHandle;
}
public void Dispose()
{
if (!_disposed)
{
// Cleanup callback handles
if (_evaluationCallbackHandle.HasValue && _evaluationCallbackHandle.Value.IsAllocated)
_evaluationCallbackHandle.Value.Free();
if (_evaluationNativeCallbackHandle.HasValue && _evaluationNativeCallbackHandle.Value.IsAllocated)
_evaluationNativeCallbackHandle.Value.Free();
_handle?.Dispose();
_disposed = true;
}
}
}