SSPC-Tester/SSPCTester.Logic/Calculation/CurveMetrics.cs
2026-07-05 13:33:55 +08:00

173 lines
6.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using SSPCTester.Devices.Interfaces;
namespace SSPCTester.Logic.Calculation;
/// <summary>
/// 曲线指标计算(纯函数,可单测)。
/// 覆盖开发文档 §6.4 中 1.1-1.6 指标。
/// </summary>
public static class CurveMetrics
{
/// <summary>查找第一个达到 vTarget 的样点索引;找不到返回 -1。</summary>
public static int FirstReachIndex(double[] v, double vTarget, bool rising = true, int startIndex = 0)
{
if (v == null || v.Length == 0) return -1;
if (startIndex < 0) startIndex = 0;
for (int i = startIndex; i < v.Length; i++)
{
if (rising ? v[i] >= vTarget : v[i] <= vTarget) return i;
}
return -1;
}
/// <summary>
/// 开启时间ms控制信号发出curve.TriggerTimeSec到电压达到 Vmax 的 thresholdRatio。
/// </summary>
public static double OnTimeMs(CurveData curve, double vMax, double thresholdRatio = 0.9)
{
int idx = FirstReachIndex(curve.Voltage, vMax * thresholdRatio, rising: true);
if (idx < 0) return double.NaN;
double t = curve.TimeAt(idx) - curve.TriggerTimeSec;
return t * 1000.0;
}
/// <summary>
/// 关闭时间ms控制信号撤销到电压降到 Vmax 的 thresholdRatio如 10%)。
/// </summary>
public static double OffTimeMs(CurveData curve, double vMax, double thresholdRatio = 0.1)
{
int idx = FirstReachIndex(curve.Voltage, vMax * thresholdRatio, rising: false);
if (idx < 0) return double.NaN;
double t = curve.TimeAt(idx) - curve.TriggerTimeSec;
return t * 1000.0;
}
/// <summary>
/// 上升时间ms电压从 Vmax * low 到 Vmax * high 的时间(默认 10% → 90%)。
/// </summary>
public static double RiseTimeMs(CurveData curve, double vMax, double lowRatio = 0.1, double highRatio = 0.9)
{
var mark = AnalyzeRising(curve, vMax, lowRatio, highRatio);
return mark.IsValid ? mark.DurationMs : double.NaN;
}
/// <summary>
/// 下降时间ms电压从 Vmax * high 到 Vmax * low 的时间(默认 90% → 10%)。
/// </summary>
public static double FallTimeMs(CurveData curve, double vMax, double highRatio = 0.9, double lowRatio = 0.1)
{
var mark = AnalyzeFalling(curve, vMax, highRatio, lowRatio);
return mark.IsValid ? mark.DurationMs : double.NaN;
}
/// <summary>Rising curve 10% - 90% transition mark.</summary>
public static CurveTransitionMark AnalyzeRising(CurveData curve, double vMax, double lowRatio = 0.1, double highRatio = 0.9)
{
double startThreshold = 0;
double endThreshold = vMax * highRatio;
int triggerIndex = TriggerIndex(curve);
int endIndex = FirstReachIndex(curve.Voltage, endThreshold, rising: true, startIndex: triggerIndex);
if (endIndex < 0) return CurveTransitionMark.Invalid(startThreshold, endThreshold);
int startIndex = LastReachIndex(curve.Voltage, startThreshold, rising: false, startIndex: triggerIndex, endIndex: endIndex);
if (startIndex < 0) startIndex = FirstReachIndex(curve.Voltage, startThreshold, rising: true, startIndex: triggerIndex);
if (startIndex < 0 || startIndex > endIndex) return CurveTransitionMark.Invalid(startThreshold, endThreshold);
return BuildTransitionMark(curve, startIndex, endIndex, startThreshold, endThreshold);
}
/// <summary>Falling curve 90% - 10% transition mark.</summary>
public static CurveTransitionMark AnalyzeFalling(CurveData curve, double vMax, double highRatio = 0.9, double lowRatio = 0.1)
{
double startThreshold = vMax;
double endThreshold = vMax * lowRatio;
int triggerIndex = TriggerIndex(curve);
int endIndex = FirstReachIndex(curve.Voltage, endThreshold, rising: false, startIndex: triggerIndex);
if (endIndex < 0) return CurveTransitionMark.Invalid(startThreshold, endThreshold);
int startIndex = LastReachIndex(curve.Voltage, vMax * 0.98, rising: true, startIndex: triggerIndex, endIndex: endIndex);
if (startIndex < 0) startIndex = triggerIndex;
if (startIndex > endIndex) return CurveTransitionMark.Invalid(startThreshold, endThreshold);
return BuildTransitionMark(curve, startIndex, endIndex, startThreshold, endThreshold);
}
private static int TriggerIndex(CurveData curve)
{
if (curve.TriggerSampleIndex > 0)
return Math.Min(curve.TriggerSampleIndex, Math.Max(curve.Voltage.Length - 1, 0));
if (curve.SampleRateHz <= 0)
return 0;
int index = (int)Math.Round(curve.TriggerTimeSec * curve.SampleRateHz);
return Math.Clamp(index, 0, Math.Max(curve.Voltage.Length - 1, 0));
}
private static int LastReachIndex(double[] v, double vTarget, bool rising, int startIndex, int endIndex)
{
if (v == null || v.Length == 0) return -1;
startIndex = Math.Clamp(startIndex, 0, v.Length - 1);
endIndex = Math.Clamp(endIndex, 0, v.Length - 1);
for (int i = endIndex; i >= startIndex; i--)
{
if (rising ? v[i] >= vTarget : v[i] <= vTarget) return i;
}
return -1;
}
private static CurveTransitionMark BuildTransitionMark(
CurveData curve,
int startIndex,
int endIndex,
double startThreshold,
double endThreshold)
{
double startTimeMs = (curve.TimeAt(startIndex) - curve.TriggerTimeSec) * 1000.0;
double endTimeMs = (curve.TimeAt(endIndex) - curve.TriggerTimeSec) * 1000.0;
return new CurveTransitionMark(
startIndex,
endIndex,
startTimeMs,
endTimeMs,
endTimeMs - startTimeMs,
startThreshold,
endThreshold,
true);
}
/// <summary>从波形最大值估算 Vmax去掉前后噪声取 95 百分位以上的均值)。</summary>
public static double EstimateVMax(double[] v)
{
if (v == null || v.Length == 0) return 0;
var sorted = (double[])v.Clone();
Array.Sort(sorted);
int from = (int)(sorted.Length * 0.95);
double sum = 0; int n = 0;
for (int i = from; i < sorted.Length; i++) { sum += sorted[i]; n++; }
return n > 0 ? sum / n : sorted[^1];
}
}
public readonly record struct CurveTransitionMark(
int StartIndex,
int EndIndex,
double StartTimeMs,
double EndTimeMs,
double DurationMs,
double StartThresholdV,
double EndThresholdV,
bool IsValid)
{
public static CurveTransitionMark Invalid(double startThresholdV, double endThresholdV) =>
new(
-1,
-1,
double.NaN,
double.NaN,
double.NaN,
startThresholdV,
endThresholdV,
false);
}