2025-11-28 13:35:16 -08:00
using System.Numerics ;
using System.Runtime.CompilerServices ;
namespace QuanTAlib ;
/// <summary>
/// SIMD-accelerated extension methods for high-performance array operations.
/// Uses Vector<T> for 4-8x speedup on supported hardware with automatic scalar fallback.
/// </summary>
public static class SimdExtensions
{
2025-11-29 16:08:45 -08:00
// Internal scalar implementations for testability
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool ContainsNonFiniteScalar ( ReadOnlySpan < double > span )
{
for ( int i = 0 ; i < span . Length ; i ++)
{
if (! double . IsFinite ( span [ i ]))
return true ;
}
return false ;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static double SumScalar ( ReadOnlySpan < double > span )
{
double scalar = 0.0 ;
for ( int i = 0 ; i < span . Length ; i ++)
scalar += span [ i ];
return scalar ;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static double MinScalar ( ReadOnlySpan < double > span )
{
double min = span [ 0 ];
for ( int i = 1 ; i < span . Length ; i ++)
{
if ( span [ i ] < min )
min = span [ i ];
}
return min ;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static double MaxScalar ( ReadOnlySpan < double > span )
{
double max = span [ 0 ];
for ( int i = 1 ; i < span . Length ; i ++)
{
if ( span [ i ] > max )
max = span [ i ];
}
return max ;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static double VarianceScalar ( ReadOnlySpan < double > span , double mean )
{
double sumSquares = 0.0 ;
for ( int i = 0 ; i < span . Length ; i ++)
{
double diff = span [ i ] - mean ;
sumSquares += diff * diff ;
}
return sumSquares / ( span . Length - 1 );
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static ( double Min , double Max ) MinMaxScalar ( ReadOnlySpan < double > span )
{
double scalarMin = span [ 0 ];
double scalarMax = span [ 0 ];
for ( int i = 1 ; i < span . Length ; i ++)
{
if ( span [ i ] < scalarMin ) scalarMin = span [ i ];
if ( span [ i ] > scalarMax ) scalarMax = span [ i ];
}
return ( scalarMin , scalarMax );
}
2025-11-29 13:19:04 -08:00
/// <summary>
/// Checks if span contains any non-finite values (NaN or Infinity).
/// Returns true if any non-finite value is found.
2025-11-29 16:43:52 -08:00
/// Uses SIMD: NaN detected via v != v (NaN is the only value where this is true),
/// Infinity detected via |v| > MaxValue comparison.
2025-11-29 13:19:04 -08:00
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ContainsNonFinite ( this ReadOnlySpan < double > span )
{
if ( span . IsEmpty ) return false ;
if ( Vector . IsHardwareAccelerated && span . Length >= Vector < double >. Count )
{
int vectorSize = Vector < double >. Count ;
int i = 0 ;
2025-11-29 16:43:52 -08:00
var maxValue = new Vector < double >( double . MaxValue );
2025-11-29 13:19:04 -08:00
for (; i <= span . Length - vectorSize ; i += vectorSize )
{
var vector = new Vector < double >( span . Slice ( i , vectorSize ));
2025-11-29 16:43:52 -08:00
// NaN check: NaN != NaN, so Vector.Equals(v, v) will be false for NaN lanes
var nanCheck = Vector . Equals ( vector , vector );
if (! nanCheck . Equals ( Vector < long >. AllBitsSet ))
return true ;
// Infinity check: |v| > MaxValue (Infinity has magnitude > MaxValue)
var absVec = Vector . Abs ( vector );
var infCheck = Vector . GreaterThan ( absVec , maxValue );
if (! infCheck . Equals ( Vector < long >. Zero ))
return true ;
2025-11-29 13:19:04 -08:00
}
for (; i < span . Length ; i ++)
{
if (! double . IsFinite ( span [ i ]))
return true ;
}
return false ;
}
2025-11-29 16:08:45 -08:00
return ContainsNonFiniteScalar ( span );
2025-11-29 13:19:04 -08:00
}
2025-11-28 13:35:16 -08:00
/// <summary>
/// Calculates sum using SIMD vectorization when available.
/// 4-8x faster than scalar loop on AVX2/AVX-512 hardware.
2025-11-29 13:19:04 -08:00
/// Returns NaN if any input value is non-finite.
2025-11-28 13:35:16 -08:00
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double SumSIMD ( this ReadOnlySpan < double > span )
{
if ( span . IsEmpty ) return 0.0 ;
2025-11-29 13:19:04 -08:00
// Guard against non-finite inputs
if ( span . ContainsNonFinite ()) return double . NaN ;
2025-11-28 13:35:16 -08:00
if ( Vector . IsHardwareAccelerated && span . Length >= Vector < double >. Count )
{
Vector < double > sum = Vector < double >. Zero ;
int vectorSize = Vector < double >. Count ;
int i = 0 ;
for (; i <= span . Length - vectorSize ; i += vectorSize )
{
var vector = new Vector < double >( span . Slice ( i , vectorSize ));
sum += vector ;
}
double result = 0.0 ;
for ( int j = 0 ; j < vectorSize ; j ++)
result += sum [ j ];
for (; i < span . Length ; i ++)
result += span [ i ];
return result ;
}
2025-11-29 16:08:45 -08:00
return SumScalar ( span );
2025-11-28 13:35:16 -08:00
}
/// <summary>
/// Calculates minimum value using SIMD vectorization when available.
/// 4-6x faster than scalar loop on AVX2/AVX-512 hardware.
2025-11-29 13:19:04 -08:00
/// Returns NaN if any input value is non-finite.
2025-11-28 13:35:16 -08:00
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double MinSIMD ( this ReadOnlySpan < double > span )
{
if ( span . IsEmpty ) return double . NaN ;
if ( span . Length == 1 ) return span [ 0 ];
2025-11-29 13:19:04 -08:00
// Guard against non-finite inputs
if ( span . ContainsNonFinite ()) return double . NaN ;
2025-11-28 13:35:16 -08:00
if ( Vector . IsHardwareAccelerated && span . Length >= Vector < double >. Count )
{
int vectorSize = Vector < double >. Count ;
var minVec = new Vector < double >( span . Slice ( 0 , vectorSize ));
int i = vectorSize ;
for (; i <= span . Length - vectorSize ; i += vectorSize )
{
var vector = new Vector < double >( span . Slice ( i , vectorSize ));
minVec = Vector . Min ( minVec , vector );
}
double result = minVec [ 0 ];
for ( int j = 1 ; j < vectorSize ; j ++)
{
if ( minVec [ j ] < result )
result = minVec [ j ];
}
for (; i < span . Length ; i ++)
{
if ( span [ i ] < result )
result = span [ i ];
}
return result ;
}
2025-11-29 16:08:45 -08:00
return MinScalar ( span );
2025-11-28 13:35:16 -08:00
}
/// <summary>
/// Calculates maximum value using SIMD vectorization when available.
/// 4-6x faster than scalar loop on AVX2/AVX-512 hardware.
2025-11-29 13:19:04 -08:00
/// Returns NaN if any input value is non-finite.
2025-11-28 13:35:16 -08:00
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double MaxSIMD ( this ReadOnlySpan < double > span )
{
if ( span . IsEmpty ) return double . NaN ;
if ( span . Length == 1 ) return span [ 0 ];
2025-11-29 13:19:04 -08:00
// Guard against non-finite inputs
if ( span . ContainsNonFinite ()) return double . NaN ;
2025-11-28 13:35:16 -08:00
if ( Vector . IsHardwareAccelerated && span . Length >= Vector < double >. Count )
{
int vectorSize = Vector < double >. Count ;
var maxVec = new Vector < double >( span . Slice ( 0 , vectorSize ));
int i = vectorSize ;
for (; i <= span . Length - vectorSize ; i += vectorSize )
{
var vector = new Vector < double >( span . Slice ( i , vectorSize ));
maxVec = Vector . Max ( maxVec , vector );
}
double result = maxVec [ 0 ];
for ( int j = 1 ; j < vectorSize ; j ++)
{
if ( maxVec [ j ] > result )
result = maxVec [ j ];
}
for (; i < span . Length ; i ++)
{
if ( span [ i ] > result )
result = span [ i ];
}
return result ;
}
2025-11-29 16:08:45 -08:00
return MaxScalar ( span );
2025-11-28 13:35:16 -08:00
}
/// <summary>
/// Calculates average using SIMD vectorization when available.
/// 4-8x faster than scalar loop on AVX2/AVX-512 hardware.
2025-11-29 13:19:04 -08:00
/// Returns NaN if any input value is non-finite.
2025-11-28 13:35:16 -08:00
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double AverageSIMD ( this ReadOnlySpan < double > span )
{
if ( span . IsEmpty ) return double . NaN ;
2025-11-29 13:19:04 -08:00
// SumSIMD already guards against non-finite, which will propagate NaN
2025-11-28 13:35:16 -08:00
return span . SumSIMD () / span . Length ;
}
/// <summary>
2025-12-10 14:51:58 -05:00
/// Calculates variance using a two-pass SIMD variant that computes the mean first (via AverageSIMD) and then sums squared differences to produce variance.
/// Note that this is not the single-pass Welford algorithm.
2025-11-29 13:19:04 -08:00
/// Returns NaN if any input value is non-finite or if mean is non-finite.
2025-11-28 13:35:16 -08:00
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double VarianceSIMD ( this ReadOnlySpan < double > span , double? mean = null )
{
if ( span . Length < 2 ) return double . NaN ;
2025-12-04 13:49:05 -08:00
double m ;
if ( mean . HasValue )
{
if ( span . ContainsNonFinite ()) return double . NaN ;
m = mean . Value ;
}
else
{
m = span . AverageSIMD ();
}
2025-11-28 13:35:16 -08:00
2025-11-29 13:19:04 -08:00
if (! double . IsFinite ( m )) return double . NaN ;
2025-11-28 13:35:16 -08:00
if ( Vector . IsHardwareAccelerated && span . Length >= Vector < double >. Count )
{
var meanVec = new Vector < double >( m );
Vector < double > sumSq = Vector < double >. Zero ;
int vectorSize = Vector < double >. Count ;
int i = 0 ;
for (; i <= span . Length - vectorSize ; i += vectorSize )
{
var vector = new Vector < double >( span . Slice ( i , vectorSize ));
var diff = vector - meanVec ;
sumSq += diff * diff ;
}
double result = 0.0 ;
for ( int j = 0 ; j < vectorSize ; j ++)
result += sumSq [ j ];
for (; i < span . Length ; i ++)
{
double diff = span [ i ] - m ;
result += diff * diff ;
}
return result / ( span . Length - 1 );
}
2025-11-29 16:08:45 -08:00
return VarianceScalar ( span , m );
2025-11-28 13:35:16 -08:00
}
/// <summary>
/// Calculates standard deviation using SIMD vectorization.
2025-11-29 13:19:04 -08:00
/// Returns NaN if any input value is non-finite or if mean is non-finite.
2025-11-28 13:35:16 -08:00
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double StdDevSIMD ( this ReadOnlySpan < double > span , double? mean = null )
{
2025-11-29 13:19:04 -08:00
// VarianceSIMD already guards against non-finite, which will propagate NaN through Sqrt
2025-11-28 13:35:16 -08:00
return Math . Sqrt ( span . VarianceSIMD ( mean ));
}
/// <summary>
/// Finds both min and max in a single pass using SIMD vectorization.
/// More efficient than calling MinSIMD and MaxSIMD separately.
2025-11-29 13:19:04 -08:00
/// Returns (NaN, NaN) if any input value is non-finite.
2025-11-28 13:35:16 -08:00
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ( double Min , double Max ) MinMaxSIMD ( this ReadOnlySpan < double > span )
{
if ( span . IsEmpty ) return ( double . NaN , double . NaN );
if ( span . Length == 1 ) return ( span [ 0 ], span [ 0 ]);
2025-11-29 13:19:04 -08:00
// Guard against non-finite inputs
if ( span . ContainsNonFinite ()) return ( double . NaN , double . NaN );
2025-11-28 13:35:16 -08:00
if ( Vector . IsHardwareAccelerated && span . Length >= Vector < double >. Count )
{
int vectorSize = Vector < double >. Count ;
var minVec = new Vector < double >( span . Slice ( 0 , vectorSize ));
var maxVec = minVec ;
int i = vectorSize ;
for (; i <= span . Length - vectorSize ; i += vectorSize )
{
var vector = new Vector < double >( span . Slice ( i , vectorSize ));
minVec = Vector . Min ( minVec , vector );
maxVec = Vector . Max ( maxVec , vector );
}
double min = minVec [ 0 ];
double max = maxVec [ 0 ];
for ( int j = 1 ; j < vectorSize ; j ++)
{
if ( minVec [ j ] < min ) min = minVec [ j ];
if ( maxVec [ j ] > max ) max = maxVec [ j ];
}
for (; i < span . Length ; i ++)
{
if ( span [ i ] < min ) min = span [ i ];
if ( span [ i ] > max ) max = span [ i ];
}
return ( min , max );
}
2025-11-29 16:08:45 -08:00
return MinMaxScalar ( span );
2025-11-28 13:35:16 -08:00
}
}