207 lines
7.3 KiB
C#
207 lines
7.3 KiB
C#
using SSPCTester.Devices.Interfaces;
|
||
using SSPCTester.Logic.Calculation;
|
||
using Xunit;
|
||
|
||
namespace SSPCTester.Tests;
|
||
|
||
public class CurveMetricsTests
|
||
{
|
||
/// <summary>构造一段 V(t) = Vmax*(1-exp(-t/tau)) 形状的上升曲线。</summary>
|
||
private static CurveData MakeRising(double vMax = 28, double tauSec = 0.0005, int rate = 100_000, double duration = 0.01, double trigger = 0.0025)
|
||
{
|
||
int n = (int)(rate * duration);
|
||
var v = new double[n];
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
double t = (double)i / rate - trigger;
|
||
v[i] = t <= 0 ? 0 : vMax * (1 - System.Math.Exp(-t / tauSec));
|
||
}
|
||
return new CurveData { SampleRateHz = rate, TriggerTimeSec = trigger, Voltage = v };
|
||
}
|
||
|
||
private static CurveData MakeFalling(double vMax = 28, double tauSec = 0.0005, int rate = 100_000, double duration = 0.01, double trigger = 0.0025)
|
||
{
|
||
int n = (int)(rate * duration);
|
||
var v = new double[n];
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
double t = (double)i / rate - trigger;
|
||
v[i] = t <= 0 ? vMax : vMax * System.Math.Exp(-t / tauSec);
|
||
}
|
||
return new CurveData { SampleRateHz = rate, TriggerTimeSec = trigger, Voltage = v };
|
||
}
|
||
|
||
[Fact]
|
||
public void OnTimeMs_ReachesNinetyPercent_AtAboutMinusLogPointOneTau()
|
||
{
|
||
var c = MakeRising();
|
||
double onMs = CurveMetrics.OnTimeMs(c, 28, 0.9);
|
||
// 90% 时刻 t = -tau * ln(0.1) ≈ 1.151 ms
|
||
Assert.InRange(onMs, 1.1, 1.2);
|
||
}
|
||
|
||
[Fact]
|
||
public void RiseTimeMs_TonToNinety_AboutNinetyPercentTime()
|
||
{
|
||
var c = MakeRising();
|
||
double rt = CurveMetrics.RiseTimeMs(c, 28);
|
||
// ln(0.9/0.1) = ln 9 ≈ 2.197;rt = tau * 2.197 ≈ 1.099 ms
|
||
Assert.InRange(rt, 1.1, 1.2);
|
||
}
|
||
|
||
[Fact]
|
||
public void AnalyzeRising_ReturnsTonToNinetyIndexesAndDuration()
|
||
{
|
||
var c = MakeRising();
|
||
var mark = CurveMetrics.AnalyzeRising(c, 28);
|
||
int triggerIndex = (int)Math.Round(c.TriggerTimeSec * c.SampleRateHz);
|
||
int expectedEnd = CurveMetrics.FirstReachIndex(c.Voltage, 28 * 0.9, rising: true, triggerIndex);
|
||
int expectedStart = triggerIndex + 1;
|
||
|
||
Assert.True(mark.IsValid);
|
||
Assert.Equal(expectedStart, mark.StartIndex);
|
||
Assert.Equal(expectedEnd, mark.EndIndex);
|
||
Assert.Equal(c.Voltage[expectedStart], mark.StartThresholdV, 6);
|
||
Assert.Equal((c.TimeAt(expectedEnd) - c.TimeAt(expectedStart)) * 1000.0, mark.DurationMs, 6);
|
||
Assert.Equal(mark.DurationMs, CurveMetrics.RiseTimeMs(c, 28), 6);
|
||
}
|
||
|
||
[Fact]
|
||
public void OffTimeMs_FallingToTenPercent()
|
||
{
|
||
var c = MakeFalling();
|
||
double off = CurveMetrics.OffTimeMs(c, 28, 0.1);
|
||
Assert.InRange(off, 1.1, 1.2);
|
||
}
|
||
|
||
[Fact]
|
||
public void AnalyzeFalling_ReturnsStableHighToTenIndexesAndDuration()
|
||
{
|
||
var c = MakeFalling();
|
||
var mark = CurveMetrics.AnalyzeFalling(c, 28);
|
||
int triggerIndex = (int)Math.Round(c.TriggerTimeSec * c.SampleRateHz);
|
||
int expectedEnd = CurveMetrics.FirstReachIndex(c.Voltage, 28 * 0.1, rising: false, triggerIndex);
|
||
int expectedStart = triggerIndex + 1;
|
||
|
||
Assert.True(mark.IsValid);
|
||
Assert.Equal(expectedStart, mark.StartIndex);
|
||
Assert.Equal(expectedEnd, mark.EndIndex);
|
||
Assert.Equal(c.Voltage[expectedStart], mark.StartThresholdV, 6);
|
||
Assert.Equal((c.TimeAt(expectedEnd) - c.TimeAt(expectedStart)) * 1000.0, mark.DurationMs, 6);
|
||
Assert.Equal(mark.DurationMs, CurveMetrics.FallTimeMs(c, 28), 6);
|
||
}
|
||
|
||
[Fact]
|
||
public void AnalyzeRising_StartsAtSustainedRiseAndEndsAtFirstRawNinetyPercent()
|
||
{
|
||
const int rate = 1_000_000;
|
||
const double trigger = 0.0002;
|
||
int triggerIndex = (int)Math.Round(trigger * rate);
|
||
var v = Enumerable.Repeat(0.0, 2_000).ToArray();
|
||
|
||
v[triggerIndex + 100] = -4.0;
|
||
v[triggerIndex + 101] = 2.0;
|
||
v[triggerIndex + 102] = 0.0;
|
||
|
||
int riseStart = triggerIndex + 500;
|
||
for (int i = riseStart; i < v.Length; i++)
|
||
{
|
||
double progress = Math.Clamp((i - riseStart) / 60.0, 0.0, 1.0);
|
||
v[i] = 5.0 * progress;
|
||
}
|
||
|
||
var curve = new CurveData { SampleRateHz = rate, TriggerTimeSec = trigger, Voltage = v };
|
||
var mark = CurveMetrics.AnalyzeRising(curve, 5.0);
|
||
int expectedEnd = CurveMetrics.FirstReachIndex(v, 4.5, rising: true, riseStart);
|
||
|
||
Assert.True(mark.IsValid);
|
||
Assert.InRange(mark.StartIndex, riseStart - 3, riseStart + 5);
|
||
Assert.Equal(expectedEnd, mark.EndIndex);
|
||
}
|
||
|
||
[Fact]
|
||
public void AnalyzeFalling_StartsAtSlowDropAndEndsAtFirstRawTenPercent()
|
||
{
|
||
const int rate = 1_000_000;
|
||
const double trigger = 0.0002;
|
||
int triggerIndex = (int)Math.Round(trigger * rate);
|
||
var v = Enumerable.Repeat(3.0, 2_000).ToArray();
|
||
|
||
int fallStart = triggerIndex + 300;
|
||
for (int i = fallStart; i < triggerIndex + 850; i++)
|
||
v[i] = 3.0 - 0.55 * (i - fallStart) / 550.0;
|
||
|
||
v[triggerIndex + 850] = 5.0;
|
||
for (int i = triggerIndex + 851; i < v.Length; i++)
|
||
{
|
||
double progress = Math.Clamp((i - (triggerIndex + 851)) / 45.0, 0.0, 1.0);
|
||
v[i] = Math.Max(-0.2, 2.45 * (1.0 - progress));
|
||
}
|
||
|
||
var curve = new CurveData { SampleRateHz = rate, TriggerTimeSec = trigger, Voltage = v };
|
||
var mark = CurveMetrics.AnalyzeFalling(curve, 3.0);
|
||
int expectedEnd = CurveMetrics.FirstReachIndex(v, 0.3, rising: false, fallStart);
|
||
|
||
Assert.True(mark.IsValid);
|
||
Assert.InRange(mark.StartIndex, fallStart - 5, fallStart + 30);
|
||
Assert.Equal(expectedEnd, mark.EndIndex);
|
||
}
|
||
|
||
[Fact]
|
||
public void AnalyzeTransition_ReturnsInvalidWhenThresholdsAreNotReached()
|
||
{
|
||
var risingCurve = new CurveData
|
||
{
|
||
SampleRateHz = 100_000,
|
||
TriggerTimeSec = 0,
|
||
Voltage = Enumerable.Repeat(0.0, 100).ToArray()
|
||
};
|
||
var fallingCurve = new CurveData
|
||
{
|
||
SampleRateHz = 100_000,
|
||
TriggerTimeSec = 0,
|
||
Voltage = Enumerable.Repeat(28.0, 100).ToArray()
|
||
};
|
||
|
||
var rising = CurveMetrics.AnalyzeRising(risingCurve, 28);
|
||
var falling = CurveMetrics.AnalyzeFalling(fallingCurve, 28);
|
||
|
||
Assert.False(rising.IsValid);
|
||
Assert.False(falling.IsValid);
|
||
Assert.Equal(-1, rising.StartIndex);
|
||
Assert.Equal(-1, falling.EndIndex);
|
||
Assert.True(double.IsNaN(CurveMetrics.RiseTimeMs(risingCurve, 28)));
|
||
Assert.True(double.IsNaN(CurveMetrics.FallTimeMs(fallingCurve, 28)));
|
||
}
|
||
|
||
[Fact]
|
||
public void EstimateVMax_NearActualMax()
|
||
{
|
||
var c = MakeRising();
|
||
double v = CurveMetrics.EstimateVMax(c.Voltage);
|
||
Assert.InRange(v, 27, 28.001);
|
||
}
|
||
}
|
||
|
||
public class PowerLossTests
|
||
{
|
||
[Fact]
|
||
public void VoltageDrop_Subtracts()
|
||
=> Assert.Equal(1.5, PowerLoss.VoltageDrop(28, 26.5), 3);
|
||
|
||
[Fact]
|
||
public void Loss_PinMinusPout()
|
||
=> Assert.Equal(28 * 1.0 - 26.5 * 1.0, PowerLoss.LossWatts(28, 1, 26.5, 1), 3);
|
||
|
||
[Fact]
|
||
public void Efficiency_Percentage()
|
||
{
|
||
double e = PowerLoss.EfficiencyPct(28, 1, 26.5, 1);
|
||
Assert.InRange(e, 94, 95);
|
||
}
|
||
|
||
[Fact]
|
||
public void Efficiency_ZeroInputReturnsZero()
|
||
=> Assert.Equal(0.0, PowerLoss.EfficiencyPct(0, 0, 0, 0));
|
||
}
|