Enhance documentation and improve validation in AFIRMA, ALMA, and Bessel implementations

This commit is contained in:
Miha Kralj
2025-12-30 22:14:57 -08:00
parent 78a3a25ada
commit fa6cb1d623
8 changed files with 309 additions and 158 deletions
+34 -3
View File
@@ -6,9 +6,14 @@ public class BesselTests
[Fact]
public void Bessel_Constructor_Length_ValidatesInput()
{
Assert.Throws<ArgumentException>(() => new Bessel(0));
Assert.Throws<ArgumentException>(() => new Bessel(-1));
Assert.Throws<ArgumentException>(() => new Bessel(1));
var ex0 = Assert.Throws<ArgumentException>(() => new Bessel(0));
Assert.Equal("length", ex0.ParamName);
var exNeg = Assert.Throws<ArgumentException>(() => new Bessel(-1));
Assert.Equal("length", exNeg.ParamName);
var ex1 = Assert.Throws<ArgumentException>(() => new Bessel(1));
Assert.Equal("length", ex1.ParamName);
var bessel = new Bessel(2);
Assert.NotNull(bessel);
@@ -17,6 +22,32 @@ public class BesselTests
Assert.NotNull(bessel14);
}
[Fact]
public void Bessel_SpanCalculate_ValidatesLength()
{
double[] source = [1, 2, 3, 4, 5];
double[] output = new double[5];
var exLength = Assert.Throws<ArgumentException>(() =>
Bessel.Calculate(source.AsSpan(), output.AsSpan(), 1));
Assert.Equal("length", exLength.ParamName);
var exLengthZero = Assert.Throws<ArgumentException>(() =>
Bessel.Calculate(source.AsSpan(), output.AsSpan(), 0));
Assert.Equal("length", exLengthZero.ParamName);
}
[Fact]
public void Bessel_SpanCalculate_ValidatesBufferLength()
{
double[] source = [1, 2, 3, 4, 5];
double[] wrongSizeOutput = new double[3];
var ex = Assert.Throws<ArgumentException>(() =>
Bessel.Calculate(source.AsSpan(), wrongSizeOutput.AsSpan(), 14));
Assert.Equal("output", ex.ParamName);
}
[Fact]
public void Bessel_Calc_ReturnsValue()
{
+37 -9
View File
@@ -125,6 +125,21 @@ public sealed class Bessel : AbstractBase, IDisposable
return;
}
// Warmup phase: pass-through until enough history (Count >= 2)
for (; i < len && _state.Count < 2; i++)
{
double val = source[i];
if (double.IsFinite(val))
_state.LastValidValue = val;
else
val = _state.LastValidValue;
_state.F2 = _state.F1;
_state.F1 = val;
_state.Count++;
}
// Hot phase: main filtering loop (no warmup check)
for (; i < len; i++)
{
double val = source[i];
@@ -133,10 +148,8 @@ public sealed class Bessel : AbstractBase, IDisposable
else
val = _state.LastValidValue;
double filt = _state.Count < 3
? val
: Math.FusedMultiplyAdd(_c3, _state.F2,
Math.FusedMultiplyAdd(_c2, _state.F1, _c1 * val));
double filt = Math.FusedMultiplyAdd(_c3, _state.F2,
Math.FusedMultiplyAdd(_c2, _state.F1, _c1 * val));
_state.F2 = _state.F1;
_state.F1 = filt;
@@ -183,7 +196,8 @@ public sealed class Bessel : AbstractBase, IDisposable
_state.F2 = val;
}
double filt = _state.Count < 3
// 2nd-order filter needs 2 history points (Count >= 2)
double filt = _state.Count < 2
? val
: Math.FusedMultiplyAdd(_c3, _state.F2,
Math.FusedMultiplyAdd(_c2, _state.F1, _c1 * val));
@@ -268,6 +282,22 @@ public sealed class Bessel : AbstractBase, IDisposable
}
}
// Warmup phase: pass-through until enough history (Count >= 2)
for (; i < len && state.Count < 2; i++)
{
double val = source[i];
if (double.IsFinite(val))
state.LastValidValue = val;
else
val = state.LastValidValue;
state.F2 = state.F1;
state.F1 = val;
output[i] = val;
state.Count++;
}
// Hot phase: main filtering loop (no warmup check)
for (; i < len; i++)
{
double val = source[i];
@@ -276,10 +306,8 @@ public sealed class Bessel : AbstractBase, IDisposable
else
val = state.LastValidValue;
double filt = state.Count < 3
? val
: Math.FusedMultiplyAdd(c3, state.F2,
Math.FusedMultiplyAdd(c2, state.F1, c1 * val));
double filt = Math.FusedMultiplyAdd(c3, state.F2,
Math.FusedMultiplyAdd(c2, state.F1, c1 * val));
state.F2 = state.F1;
state.F1 = filt;