Merge dev into main: v0.8.7 Kahan compensated summation

This commit is contained in:
Miha Kralj
2026-03-13 22:01:52 -07:00
79 changed files with 2923 additions and 2495 deletions
+30 -36
View File
@@ -19,6 +19,8 @@ namespace QuanTAlib;
/// - RAE = 1 means same as mean predictor
/// - RAE > 1 means worse than mean predictor
/// - Scale-independent ratio
///
/// Uses Kahan compensated summation to prevent floating-point drift without periodic resync.
/// </remarks>
[SkipLocalsInit]
public sealed class Rae : AbstractBase
@@ -32,14 +34,14 @@ public sealed class Rae : AbstractBase
double ActualSum,
double AbsErrorSum,
double AbsBaselineSum,
double ActualComp,
double AbsErrorComp,
double AbsBaselineComp,
double LastValidActual,
double LastValidPredicted,
int TickCount);
double LastValidPredicted);
private State _state;
private State _p_state;
private const int ResyncInterval = 1000;
public Rae(int period)
{
if (period <= 0)
@@ -93,9 +95,15 @@ public sealed class Rae : AbstractBase
if (isNew)
{
// Update actual buffer for mean calculation
// Update actual buffer for mean calculation — Kahan compensated
double removedActual = _actualBuffer.Count == _actualBuffer.Capacity ? _actualBuffer.Oldest : 0.0;
_state.ActualSum = _state.ActualSum - removedActual + actualVal;
{
double delta = actualVal - removedActual;
double y = delta - _state.ActualComp;
double t = _state.ActualSum + y;
_state.ActualComp = (t - _state.ActualSum) - y;
_state.ActualSum = t;
}
_actualBuffer.Add(actualVal);
// Calculate mean and baseline error
@@ -103,24 +111,27 @@ public sealed class Rae : AbstractBase
double absError = Math.Abs(actualVal - predictedVal);
double absBaseline = Math.Abs(actualVal - mean);
// Update error buffer
// Update error buffer — Kahan compensated
double removedError = _absErrorBuffer.Count == _absErrorBuffer.Capacity ? _absErrorBuffer.Oldest : 0.0;
_state.AbsErrorSum = _state.AbsErrorSum - removedError + absError;
{
double delta = absError - removedError;
double y = delta - _state.AbsErrorComp;
double t = _state.AbsErrorSum + y;
_state.AbsErrorComp = (t - _state.AbsErrorSum) - y;
_state.AbsErrorSum = t;
}
_absErrorBuffer.Add(absError);
// Update baseline buffer
// Update baseline buffer — Kahan compensated
double removedBaseline = _absBaselineBuffer.Count == _absBaselineBuffer.Capacity ? _absBaselineBuffer.Oldest : 0.0;
_state.AbsBaselineSum = _state.AbsBaselineSum - removedBaseline + absBaseline;
_absBaselineBuffer.Add(absBaseline);
_state.TickCount++;
if (_actualBuffer.IsFull && _state.TickCount >= ResyncInterval)
{
_state.TickCount = 0;
_state.ActualSum = _actualBuffer.RecalculateSum();
_state.AbsErrorSum = _absErrorBuffer.RecalculateSum();
_state.AbsBaselineSum = _absBaselineBuffer.RecalculateSum();
double delta = absBaseline - removedBaseline;
double y = delta - _state.AbsBaselineComp;
double t = _state.AbsBaselineSum + y;
_state.AbsBaselineComp = (t - _state.AbsBaselineSum) - y;
_state.AbsBaselineSum = t;
}
_absBaselineBuffer.Add(absBaseline);
}
else
{
@@ -294,7 +305,6 @@ public sealed class Rae : AbstractBase
output[i] = absBaselineSum > 1e-10 ? absErrorSum / absBaselineSum : 1.0;
}
int tickCount = 0;
for (; i < len; i++)
{
double act = actual[i];
@@ -337,22 +347,6 @@ public sealed class Rae : AbstractBase
}
output[i] = absBaselineSum > 1e-10 ? absErrorSum / absBaselineSum : 1.0;
tickCount++;
if (tickCount >= ResyncInterval)
{
tickCount = 0;
double recalcActual = 0, recalcError = 0, recalcBaseline = 0;
for (int k = 0; k < period; k++)
{
recalcActual += actualBuffer[k];
recalcError += absErrorBuffer[k];
recalcBaseline += absBaselineBuffer[k];
}
actualSum = recalcActual;
absErrorSum = recalcError;
absBaselineSum = recalcBaseline;
}
}
}
@@ -362,4 +356,4 @@ public sealed class Rae : AbstractBase
TSeries results = Batch(actual, predicted, period);
return (results, indicator);
}
}
}