2024-09-22 17:31:24 -07:00
using System.Collections ;
using System.Runtime.CompilerServices ;
using System.Numerics ;
namespace QuanTAlib ;
2024-10-05 15:20:13 -07:00
/// <summary>
/// Represents a circular buffer of double values with fixed capacity.
/// </summary>
/// <remarks>
/// This class provides efficient operations for adding, accessing, and manipulating
/// a fixed-size buffer of double values. It uses SIMD operations for improved performance
/// on supported hardware.
/// </remarks>
2024-10-06 06:59:26 +00:00
public class CircularBuffer : IEnumerable < double >
{
2024-09-22 17:31:24 -07:00
private readonly double [] _buffer ;
2024-09-24 16:41:26 -07:00
private int _start = 0 ;
private int _size = 0 ;
2024-09-22 17:31:24 -07:00
2024-10-05 15:20:13 -07:00
/// <summary>
/// Gets the maximum number of elements that can be contained in the buffer.
/// </summary>
2024-09-22 17:31:24 -07:00
public int Capacity { get ; }
2024-10-05 15:20:13 -07:00
/// <summary>
/// Gets the number of elements currently contained in the buffer.
/// </summary>
2024-09-22 17:31:24 -07:00
public int Count => _size ;
2024-10-05 15:20:13 -07:00
/// <summary>
/// Initializes a new instance of the CircularBuffer class with the specified capacity.
/// </summary>
/// <param name="capacity">The maximum number of elements the buffer can hold.</param>
2024-10-06 06:59:26 +00:00
public CircularBuffer ( int capacity )
{
2024-09-22 17:31:24 -07:00
Capacity = capacity ;
_buffer = GC . AllocateArray < double >( capacity , pinned : true );
}
2024-10-05 15:20:13 -07:00
/// <summary>
/// Adds an item to the buffer.
/// </summary>
/// <param name="item">The item to add to the buffer.</param>
/// <param name="isNew">Indicates whether the item is a new value or an update to the last added value.</param>
2024-09-22 17:31:24 -07:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2024-10-06 06:59:26 +00:00
public void Add ( double item , bool isNew = true )
{
if ( _size == 0 || isNew )
{
if ( _size < Capacity )
{
2024-09-22 17:31:24 -07:00
_buffer [( _start + _size ) % Capacity ] = item ;
_size ++;
2024-10-06 06:59:26 +00:00
}
else
{
2024-09-22 17:31:24 -07:00
_buffer [ _start ] = item ;
_start = ( _start + 1 ) % Capacity ;
}
2024-10-06 06:59:26 +00:00
}
else
{
2024-09-22 17:31:24 -07:00
_buffer [( _start + _size - 1 ) % Capacity ] = item ;
}
}
2024-10-05 15:20:13 -07:00
/// <summary>
/// Gets or sets the element at the specified index.
/// </summary>
/// <param name="index">The zero-based index of the element to get or set.</param>
/// <returns>The element at the specified index.</returns>
2024-10-06 06:59:26 +00:00
public double this [ Index index ]
{
2024-09-22 17:31:24 -07:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2024-10-06 06:59:26 +00:00
get
{
2024-09-22 17:31:24 -07:00
int actualIndex = index . IsFromEnd ? _size - index . Value : index . Value ;
actualIndex = Math . Clamp ( actualIndex , 0 , _size - 1 );
return _buffer [( _start + actualIndex ) % Capacity ];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2024-10-06 06:59:26 +00:00
set
{
2024-09-22 17:31:24 -07:00
int actualIndex = index . IsFromEnd ? _size - index . Value : index . Value ;
actualIndex = Math . Clamp ( actualIndex , 0 , _size - 1 );
_buffer [( _start + actualIndex ) % Capacity ] = value ;
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
2024-10-06 06:59:26 +00:00
private static void ThrowArgumentOutOfRangeException ()
{
2024-09-22 17:31:24 -07:00
throw new ArgumentOutOfRangeException ( "index" , "Index is out of range." );
}
2024-10-05 15:20:13 -07:00
/// <summary>
/// Gets the newest (most recently added) element in the buffer.
/// </summary>
/// <returns>The newest element in the buffer.</returns>
2024-09-22 17:31:24 -07:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2024-10-06 06:59:26 +00:00
public double Newest ()
{
2024-09-22 17:31:24 -07:00
if ( _size == 0 )
return 0 ;
return _buffer [( _start + _size - 1 ) % Capacity ];
}
2024-10-05 15:20:13 -07:00
/// <summary>
/// Gets the oldest element in the buffer.
/// </summary>
/// <returns>The oldest element in the buffer.</returns>
2024-09-22 17:31:24 -07:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2024-10-06 06:59:26 +00:00
public double Oldest ()
{
2024-09-22 17:31:24 -07:00
if ( _size == 0 )
ThrowInvalidOperationException ();
return _buffer [ _start ];
}
[MethodImpl(MethodImplOptions.NoInlining)]
2024-10-06 06:59:26 +00:00
private static void ThrowInvalidOperationException ()
{
2024-09-22 17:31:24 -07:00
throw new InvalidOperationException ( "Buffer is empty." );
}
2024-10-05 15:20:13 -07:00
/// <summary>
/// Returns an enumerator that iterates through the buffer.
/// </summary>
/// <returns>An enumerator for the buffer.</returns>
2024-09-22 17:31:24 -07:00
public Enumerator GetEnumerator () => new ( this );
IEnumerator < double > IEnumerable < double >. GetEnumerator () => GetEnumerator ();
IEnumerator IEnumerable . GetEnumerator () => GetEnumerator ();
2024-10-05 15:20:13 -07:00
/// <summary>
/// Represents an enumerator for the CircularBuffer.
/// </summary>
2024-10-06 06:59:26 +00:00
public struct Enumerator : IEnumerator < double >
{
2024-09-22 17:31:24 -07:00
private readonly CircularBuffer _buffer ;
private int _index ;
private double _current ;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2024-10-06 06:59:26 +00:00
internal Enumerator ( CircularBuffer buffer )
{
2024-09-22 17:31:24 -07:00
_buffer = buffer ;
_index = - 1 ;
_current = default ;
}
2024-10-05 15:20:13 -07:00
/// <summary>
/// Advances the enumerator to the next element of the buffer.
/// </summary>
/// <returns>true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.</returns>
2024-09-22 17:31:24 -07:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2024-10-06 06:59:26 +00:00
public bool MoveNext ()
{
2024-09-22 17:31:24 -07:00
if ( _index + 1 >= _buffer . _size )
return false ;
_index ++;
_current = _buffer [ _index ];
return true ;
}
2024-10-05 15:20:13 -07:00
/// <summary>
/// Gets the element in the buffer at the current position of the enumerator.
/// </summary>
2024-09-22 17:31:24 -07:00
public double Current => _current ;
object IEnumerator . Current => Current ;
2024-10-05 15:20:13 -07:00
/// <summary>
/// Sets the enumerator to its initial position, which is before the first element in the buffer.
/// </summary>
2024-10-06 06:59:26 +00:00
public void Reset ()
{
2024-09-22 17:31:24 -07:00
_index = - 1 ;
_current = default ;
}
2024-10-05 15:20:13 -07:00
/// <summary>
/// Disposes the enumerator.
/// </summary>
2024-09-24 16:41:26 -07:00
public void Dispose () { }
2024-09-22 17:31:24 -07:00
}
2024-10-05 15:20:13 -07:00
/// <summary>
/// Copies the elements of the buffer to an array, starting at a particular array index.
/// </summary>
/// <param name="destination">The one-dimensional array that is the destination of the elements copied from the buffer.</param>
/// <param name="destinationIndex">The zero-based index in array at which copying begins.</param>
2024-09-22 17:31:24 -07:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2024-10-06 06:59:26 +00:00
public void CopyTo ( double [] destination , int destinationIndex )
{
2024-09-22 17:31:24 -07:00
if ( _size == 0 )
return ;
2024-10-06 06:59:26 +00:00
if ( _start + _size <= Capacity )
{
2024-09-22 17:31:24 -07:00
Array . Copy ( _buffer , _start , destination , destinationIndex , _size );
2024-10-06 06:59:26 +00:00
}
else
{
2024-09-22 17:31:24 -07:00
int firstPartLength = Capacity - _start ;
Array . Copy ( _buffer , _start , destination , destinationIndex , firstPartLength );
Array . Copy ( _buffer , 0 , destination , destinationIndex + firstPartLength , _size - firstPartLength );
}
}
2024-10-05 15:20:13 -07:00
/// <summary>
/// Returns a read-only span over the contents of the buffer.
/// </summary>
/// <returns>A read-only span over the buffer contents.</returns>
2024-09-22 17:31:24 -07:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2024-10-06 06:59:26 +00:00
public ReadOnlySpan < double > GetSpan ()
{
2024-09-22 17:31:24 -07:00
if ( _size == 0 )
return ReadOnlySpan < double >. Empty ;
2024-10-06 06:59:26 +00:00
if ( _start + _size <= Capacity )
{
2024-09-22 17:31:24 -07:00
return new ReadOnlySpan < double >( _buffer , _start , _size );
2024-10-08 10:47:21 -07:00
<<<<<<< HEAD
2024-10-06 06:59:26 +00:00
}
else
{
2024-09-22 17:31:24 -07:00
return new ReadOnlySpan < double >( ToArray ());
2024-10-08 10:47:21 -07:00
=======
>>>>>>> dev
2024-09-22 17:31:24 -07:00
}
2024-10-06 14:44:43 -07:00
return new ReadOnlySpan < double >( ToArray ());
2024-09-22 17:31:24 -07:00
}
2024-10-05 15:20:13 -07:00
/// <summary>
/// Gets the internal buffer array.
/// </summary>
2024-09-22 17:31:24 -07:00
public double [] InternalBuffer => _buffer ;
2024-10-05 15:20:13 -07:00
/// <summary>
/// Returns a read-only span over the entire internal buffer.
/// </summary>
/// <returns>A read-only span over the entire internal buffer.</returns>
2024-09-22 17:31:24 -07:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan < double > GetInternalSpan () => _buffer . AsSpan ();
2024-10-05 15:20:13 -07:00
/// <summary>
/// Removes all elements from the buffer.
/// </summary>
2024-09-22 17:31:24 -07:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2024-10-06 06:59:26 +00:00
public void Clear ()
{
2024-09-22 17:31:24 -07:00
Array . Clear ( _buffer , 0 , _buffer . Length );
_start = 0 ;
_size = 0 ;
}
2024-10-05 15:20:13 -07:00
/// <summary>
/// Returns the maximum value in the buffer.
/// </summary>
/// <returns>The maximum value in the buffer.</returns>
2024-09-22 17:31:24 -07:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2024-10-06 06:59:26 +00:00
public double Max ()
{
2024-09-22 17:31:24 -07:00
if ( _size == 0 )
ThrowInvalidOperationException ();
return MaxSimd ();
}
2024-10-05 15:20:13 -07:00
/// <summary>
/// Returns the minimum value in the buffer.
/// </summary>
/// <returns>The minimum value in the buffer.</returns>
2024-09-22 17:31:24 -07:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2024-10-06 06:59:26 +00:00
public double Min ()
{
2024-09-22 17:31:24 -07:00
if ( _size == 0 )
ThrowInvalidOperationException ();
return MinSimd ();
}
2024-10-05 15:20:13 -07:00
/// <summary>
/// Computes the sum of all values in the buffer.
/// </summary>
/// <returns>The sum of all values in the buffer.</returns>
2024-09-22 17:31:24 -07:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2024-10-06 06:59:26 +00:00
public double Sum ()
{
2024-09-22 17:31:24 -07:00
return SumSimd ();
}
2024-10-05 15:20:13 -07:00
/// <summary>
/// Computes the average of all values in the buffer.
/// </summary>
/// <returns>The average of all values in the buffer.</returns>
2024-09-22 17:31:24 -07:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2024-10-06 06:59:26 +00:00
public double Average ()
{
2024-09-22 17:31:24 -07:00
if ( _size == 0 )
ThrowInvalidOperationException ();
return SumSimd () / _size ;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2024-10-06 06:59:26 +00:00
private double MaxSimd ()
{
2024-09-22 17:31:24 -07:00
var span = GetSpan ();
var vectorSize = Vector < double >. Count ;
var maxVector = new Vector < double >( double . MinValue );
int i = 0 ;
2024-10-06 06:59:26 +00:00
for (; i <= span . Length - vectorSize ; i += vectorSize )
{
2024-09-22 17:31:24 -07:00
maxVector = Vector . Max ( maxVector , new Vector < double >( span . Slice ( i , vectorSize )));
}
double max = double . MinValue ;
2024-10-06 06:59:26 +00:00
for ( int j = 0 ; j < vectorSize ; j ++)
{
2024-09-22 17:31:24 -07:00
max = Math . Max ( max , maxVector [ j ]);
}
2024-10-06 06:59:26 +00:00
for (; i < span . Length ; i ++)
{
2024-09-22 17:31:24 -07:00
max = Math . Max ( max , span [ i ]);
}
return max ;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2024-10-06 06:59:26 +00:00
private double MinSimd ()
{
2024-09-22 17:31:24 -07:00
var span = GetSpan ();
var vectorSize = Vector < double >. Count ;
var minVector = new Vector < double >( double . MaxValue );
int i = 0 ;
2024-10-06 06:59:26 +00:00
for (; i <= span . Length - vectorSize ; i += vectorSize )
{
2024-09-22 17:31:24 -07:00
minVector = Vector . Min ( minVector , new Vector < double >( span . Slice ( i , vectorSize )));
}
double min = double . MaxValue ;
2024-10-06 06:59:26 +00:00
for ( int j = 0 ; j < vectorSize ; j ++)
{
2024-09-22 17:31:24 -07:00
min = Math . Min ( min , minVector [ j ]);
}
2024-10-06 06:59:26 +00:00
for (; i < span . Length ; i ++)
{
2024-09-22 17:31:24 -07:00
min = Math . Min ( min , span [ i ]);
}
return min ;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2024-10-06 06:59:26 +00:00
private double SumSimd ()
{
2024-09-22 17:31:24 -07:00
var span = GetSpan ();
var vectorSize = Vector < double >. Count ;
var sumVector = Vector < double >. Zero ;
int i = 0 ;
2024-10-06 06:59:26 +00:00
for (; i <= span . Length - vectorSize ; i += vectorSize )
{
2024-09-22 17:31:24 -07:00
sumVector += new Vector < double >( span . Slice ( i , vectorSize ));
}
double sum = 0 ;
2024-10-06 06:59:26 +00:00
for ( int j = 0 ; j < vectorSize ; j ++)
{
2024-09-22 17:31:24 -07:00
sum += sumVector [ j ];
}
2024-10-06 06:59:26 +00:00
for (; i < span . Length ; i ++)
{
2024-09-22 17:31:24 -07:00
sum += span [ i ];
}
return sum ;
}
2024-10-05 15:20:13 -07:00
/// <summary>
/// Copies the buffer elements to a new array.
/// </summary>
/// <returns>An array containing copies of the buffer elements.</returns>
2024-10-06 06:59:26 +00:00
public double [] ToArray ()
{
2024-09-22 17:31:24 -07:00
double [] array = new double [ _size ];
CopyTo ( array , 0 );
return array ;
}
2024-10-05 15:20:13 -07:00
/// <summary>
/// Performs a parallel operation on the buffer elements.
/// </summary>
/// <param name="operation">The operation to perform on each partition of the buffer.</param>
2024-10-06 06:59:26 +00:00
public void ParallelOperation ( Func < double [], int , int , double > operation )
{
2024-09-22 17:31:24 -07:00
const int MinimumPartitionSize = 1024 ;
2024-10-06 06:59:26 +00:00
if ( _size < MinimumPartitionSize )
{
2024-09-22 17:31:24 -07:00
var span = GetSpan ();
var array = span . ToArray ();
operation ( array , 0 , array . Length );
return ;
}
int partitionCount = Environment . ProcessorCount ;
int partitionSize = _size / partitionCount ;
2024-10-06 06:59:26 +00:00
if ( partitionSize < MinimumPartitionSize )
{
2024-09-22 17:31:24 -07:00
partitionCount = Math . Max ( 1 , _size / MinimumPartitionSize );
partitionSize = _size / partitionCount ;
}
var buffer = ToArray ();
var results = new double [ partitionCount ];
2024-10-06 06:59:26 +00:00
Parallel . For ( 0 , partitionCount , i =>
{
2024-09-22 17:31:24 -07:00
int start = i * partitionSize ;
int length = ( i == partitionCount - 1 ) ? _size - start : partitionSize ;
results [ i ] = operation ( buffer , start , length );
});
}
}