namespace QuanTAlib.Tests; public class CovarianceSimdTests { [Fact] public void Covariance_Simd_Matches_Scalar_LargeDataset() { // Arrange const int count = 1000; // > 256 to trigger SIMD int period = 20; var gbmX = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 42); var gbmY = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 123); var dataX = new double[count]; var dataY = new double[count]; for (int i = 0; i < count; i++) { dataX[i] = gbmX.Next().Close; dataY[i] = gbmY.Next().Close; } var sourceX = new TSeries(); sourceX.Add(dataX); var sourceY = new TSeries(); sourceY.Add(dataY); // Act // This will use SIMD if available and length >= 256 var simdResult = Covariance.Calculate(sourceX, sourceY, period); // Calculate expected using scalar loop (simulating by using small chunks or manual calc, // but easier to just use the streaming update which is scalar) var scalarCov = new Covariance(period); var expectedValues = new double[count]; for (int i = 0; i < count; i++) { var res = scalarCov.Update(dataX[i], dataY[i]); expectedValues[i] = res.Value; } // Assert for (int i = 0; i < count; i++) { Assert.Equal(expectedValues[i], simdResult.Values[i], precision: 7); } } [Fact] public void Covariance_Simd_Handles_NaN_Correctly() { // Arrange int count = 500; int period = 50; var dataX = Enumerable.Range(0, count).Select(x => (double)x).ToArray(); var dataY = Enumerable.Range(0, count).Select(x => (double)x * 2).ToArray(); // Inject NaN dataX[300] = double.NaN; dataY[350] = double.NaN; var sourceX = new TSeries(); sourceX.Add(dataX); var sourceY = new TSeries(); sourceY.Add(dataY); // Act // The implementation checks for ContainsNonFinite() before using SIMD. // If NaN is present, it should fall back to Scalar. // We want to verify that the result is correct regardless of the path taken. var result = Covariance.Calculate(sourceX, sourceY, period); // Assert // Verify around the NaN values // Index 300 has NaN in X. Covariance should handle it (likely treat as 0 or propagate last valid if logic dictates, // but current implementation replaces non-finite with 0 in scalar core). // Let's verify against streaming which we know uses scalar logic // BUT: Batch implementation replaces NaN with 0, while Streaming propagates NaN. // To compare, we must feed 0 instead of NaN to streaming. var scalarCov = new Covariance(period); for (int i = 0; i < count; i++) { double x = dataX[i]; double y = dataY[i]; if (!double.IsFinite(x)) x = 0; if (!double.IsFinite(y)) y = 0; var res = scalarCov.Update(x, y); Assert.Equal(res.Value, result.Values[i], precision: 9); } } [Fact] public void Covariance_Simd_Resync_Check() { // Arrange // Create a dataset large enough to trigger resync in SIMD loop (ResyncInterval = 1000) // We need > 1000 elements processed in the SIMD loop. // The SIMD loop starts at 'period' and goes up to 'simdEnd'. // So we need length > period + 1000. int period = 10; int count = 2000; // Use simple linear data to make verification easy // y = 2x var dataX = Enumerable.Range(0, count).Select(x => (double)x).ToArray(); var dataY = Enumerable.Range(0, count).Select(x => (double)x * 2).ToArray(); var sourceX = new TSeries(); sourceX.Add(dataX); var sourceY = new TSeries(); sourceY.Add(dataY); // Act var result = Covariance.Calculate(sourceX, sourceY, period); // Assert // For y=2x, Cov(X,Y) = 2*Var(X) // Var(X) of sequence 0,1,2... is constant for fixed period? // For period 10: 0..9. Variance is constant. // Var(0..9) = 9.16666... (Population) or 10.185... (Sample)? // Let's just compare with scalar truth. var scalarCov = new Covariance(period); for (int i = 0; i < count; i++) { var res = scalarCov.Update(dataX[i], dataY[i]); Assert.Equal(res.Value, result.Values[i], precision: 9); } } }