using System.Buffers; using System.Runtime.CompilerServices; namespace QuanTAlib; /// /// LogCosh: Log-Cosh Loss /// /// /// Log-Cosh is the logarithm of the hyperbolic cosine of the error. It is a /// smooth approximation to the absolute error that is twice differentiable /// everywhere, making it suitable for gradient-based optimization. /// /// Formula: /// LogCosh = (1/n) * Σ log(cosh(actual - predicted)) /// /// Key properties: /// - Smooth and differentiable everywhere /// - Approximates L1 loss for large errors /// - Approximates L2 loss for small errors /// - Less sensitive to outliers than MSE /// - Numerically stable (uses stable computation for large values) /// [SkipLocalsInit] public sealed class LogCosh : BiInputIndicatorBase { /// /// Creates LogCosh with specified period. /// /// Number of values to average (must be > 0) public LogCosh(int period) : base(period, $"LogCosh({period})") { } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override double ComputeError(double actual, double predicted) { double error = actual - predicted; return StableLogCosh(error); } /// /// Computes log(cosh(x)) in a numerically stable way. /// For large |x|, cosh(x) ≈ exp(|x|)/2, so log(cosh(x)) ≈ |x| - log(2) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private static double StableLogCosh(double x) { double absX = Math.Abs(x); // For large values, use asymptotic approximation to avoid overflow if (absX > 20.0) { return absX - 0.6931471805599453; // log(2) } return Math.Log(Math.Cosh(x)); } /// /// Calculates LogCosh for entire series. /// public static TSeries Batch(TSeries actual, TSeries predicted, int period) => CalculateImpl(actual, predicted, period, Batch); /// /// Batch calculation using log-cosh error computation with rolling mean. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Batch(ReadOnlySpan actual, ReadOnlySpan predicted, Span output, int period) { ValidateBatchInputs(actual, predicted, output, period); int len = actual.Length; if (len == 0) { return; } const int StackAllocThreshold = 256; if (len <= StackAllocThreshold) { Span errors = stackalloc double[len]; ErrorHelpers.ComputeLogCoshErrors(actual, predicted, errors); ErrorHelpers.ApplyRollingMean(errors, output, period); } else { double[] rented = ArrayPool.Shared.Rent(len); try { Span errors = rented.AsSpan(0, len); ErrorHelpers.ComputeLogCoshErrors(actual, predicted, errors); ErrorHelpers.ApplyRollingMean(errors, output, period); } finally { ArrayPool.Shared.Return(rented); } } } public static (TSeries Results, LogCosh Indicator) Calculate(TSeries actual, TSeries predicted, int period) { var indicator = new LogCosh(period); TSeries results = Batch(actual, predicted, period); return (results, indicator); } }