Files
L007/GB32064Calculator - 副本趋于完美.cs
2026-01-08 21:12:15 +08:00

1101 lines
45 KiB
C#
Raw Permalink 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 iTextSharp.text.pdf.parser.clipper;
using MathNet.Numerics.Interpolation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConductivityApp.GBStandard
{
/// <summary>
/// GB/T 32064-2015 标准计算器(严格遵循国标)
/// 瞬态平面热源测试法计算导热系数和热扩散系数
/// </summary>
public class GB32064Calculator
{
#region
public struct BridgeConfig
{
public double Rs { get; set; } // 串联电阻 (Ω)
public double RL { get; set; } // 引线电阻 (Ω)
}
public struct ProbeConfig
{
public double R0 { get; set; } // 初始电阻 (Ω)
public double Alpha { get; set; } // 电阻温度系数 (1/K)
public double RadiusMM { get; set; } // 探头半径 (mm)
public int CoilCount { get; set; } // 探头环数
}
public struct TestConfig
{
public double P0 { get; set; } // 输出功率 (W)
public double SampleDensity { get; set; } // 样品密度 (kg/m³)
public string cmbSampleType { get; set; }
}
public struct CalculationResult
{
public bool IsValid { get; set; }
public double ThermalConductivity { get; set; } // λ - W/(m·K)
public double ThermalDiffusivity { get; set; } // α - m²/s
public double SpecificHeatCapacity { get; set; } // Cp - J/(kg·K)
public double CorrectionTime { get; set; } // tc - s
public double RSquared { get; set; }
public string ValidationMessage { get; set; }
public string CalculationLog { get; set; } // 新增:计算过程日志
}
#endregion
#region GB/T 32064-2015
private const double TAU_MAX_SQUARED_LOWER = 0.3; // τ_max²的最小值国标5.3.4要求)
private const double TAU_MAX_SQUARED_UPPER = 1.0; // τ_max²的最大值国标5.3.4要求)
private const double PROBING_DEPTH_RATIO_LOWER = 1.1; // 探测深度/探头半径最小值
private const double PROBING_DEPTH_RATIO_UPPER = 2.0; // 探测深度/探头半径最大值
private const double MIN_DATA_COUNT = 100; // 最小数据点数国标5.3.3.3
private const double MIN_TIME_INTERVAL = 0.1; // 最小时间间隔(s)国标5.3.3.3
private double PI_POW_1_5 = Math.Pow(Math.PI, 1.5); // π^(3/2)
#endregion
#region
private readonly BridgeConfig _bridge;
private readonly ProbeConfig _probe;
private readonly TestConfig _test;
private IInterpolation _tauDInterpolation;
private StringBuilder _logBuilder; // 新增:日志记录器
#endregion
#region
public GB32064Calculator(BridgeConfig bridge, ProbeConfig probe, TestConfig test)
{
_bridge = bridge;
_probe = probe;
_test = test;
_logBuilder = new StringBuilder();
LogInfo("GB32064Calculator 初始化开始");
LogInfo($"探头配置: R0={_probe.R0}Ω, α={_probe.Alpha}/K, 半径={_probe.RadiusMM}mm, 环数={_probe.CoilCount}");
LogInfo($"电桥配置: Rs={_bridge.Rs}Ω, RL={_bridge.RL}Ω");
LogInfo($"测试配置: P0={_test.P0}W, 密度={_test.SampleDensity}kg/m³");
ValidateConfigurations();
InitializeTauDTable();
LogInfo("GB32064Calculator 初始化完成");
}
#endregion
#region
private int _maxIterations = 300;
private double _rSquaredTarget = 0.95;
private double _convergenceThreshold = 1e-8;
private int _maxStagnationIterations = 30;
/// <summary>
/// 设置迭代参数
/// </summary>
public void SetIterationParameters(int maxIterations = 300, double rSquaredTarget = 0.95,
double convergenceThreshold = 1e-8, int maxStagnation = 30)
{
_maxIterations = maxIterations;
_rSquaredTarget = rSquaredTarget;
_convergenceThreshold = convergenceThreshold;
_maxStagnationIterations = maxStagnation;
LogInfo($"设置迭代参数: 最大迭代={_maxIterations}, R²目标={_rSquaredTarget:F3}");
}
#endregion
#region
private void ValidateConfigurations()
{
LogInfo("开始配置验证");
var errors = new List<string>();
if (_probe.R0 <= 0) errors.Add("探头初始电阻R0必须大于0");
if (_probe.Alpha <= 0) errors.Add("探头温度系数α必须大于0");
if (_probe.RadiusMM <= 0) errors.Add("探头半径必须大于0");
if (_probe.CoilCount < 1) errors.Add("探头环数应大于0");
if (_bridge.Rs <= 0) errors.Add("串联电阻Rs必须大于0");
if (_bridge.RL < 0) errors.Add("引线电阻RL不能为负");
if (_test.P0 <= 0) errors.Add("输出功率P0必须大于0");
if (_test.SampleDensity <= 0) errors.Add("样品密度必须大于0");
if (errors.Count > 0)
{
LogError($"配置验证失败,发现{errors.Count}个错误");
throw new ArgumentException($"配置验证失败:\n{string.Join("\n", errors)}");
}
else
{
LogInfo("配置验证通过");
}
}
#endregion
#region
private double GetDefaultAlphaBySoilType()
{
switch (_test.cmbSampleType)
{
case "干土":
//return 0.8e-6; // 干土的α更小修正为0.3e-6
return 0.5e-6; // 干土的α更小修正为0.3e-6
case "湿土":
return 1.0e-6; // 湿土典型值
case "冻土":
return 1.5e-6; // 冻土典型值
default:
return 1.0e-6; // 通用默认值
}
}
public CalculationResult Calculate(double[] timeArray, double[] deltaUArray, double currentMA = 120.0)
{
LogInfo("=".PadRight(80, '='));
LogInfo("开始计算导热系数和热扩散系数");
LogInfo($"输入数据: 时间点数={timeArray?.Length}, 电压点数={deltaUArray?.Length}, 电流={currentMA}mA");
try
{
// 1. 数据验证
LogInfo("步骤1: 数据验证");
if (!ValidateInputData(timeArray, deltaUArray))
{
LogError("输入数据验证失败");
return InvalidResult("输入数据验证失败");
}
LogInfo($"数据验证通过: 时间范围[{timeArray.Min():F4}s, {timeArray.Max():F4}s], 共{timeArray.Length}个点");
double I0 = currentMA / 1000.0; // mA转A
LogInfo($"电流转换: {currentMA}mA -> {I0:F4}A");
// 2. 计算ΔT(t) - 国标公式(3)
LogInfo("步骤2: 计算温度增量ΔT(t) - 国标公式(3)");
double[] deltaT = CalculateTemperatureIncrements(deltaUArray, I0);
LogInfo($"ΔT计算完成: 平均值={deltaT.Average():E6}K, 最大值={deltaT.Max():E6}K, 最小值={deltaT.Min():E6}K");
// 3. 迭代求解热扩散系数α和校正时间tc
LogInfo("步骤3: 迭代求解热扩散系数α和校正时间tc");
var (alpha, tc, iterationLog) = IterateThermalDiffusivity(timeArray, deltaT);
LogInfo(iterationLog); // 记录迭代过程
LogInfo($"迭代完成: α={alpha:E6} m²/s, tc={tc:F4}s");
// 4. 验证τ_max²是否符合国标要求
LogInfo("步骤4: 验证τ_max²是否符合国标要求");
double r = _probe.RadiusMM / 1000.0;
double tmax = timeArray.Max();
double tauMaxSquared = CalculateTauMaxSquared(tmax, tc, alpha, r);
LogInfo($"τ_max²计算: tmax={tmax:F2}s, tc={tc:F4}s, α={alpha:E6} m²/s, r={r:F6}m");
LogInfo($"τ_max²结果: {tauMaxSquared:F4} (要求范围: [{TAU_MAX_SQUARED_LOWER}, {TAU_MAX_SQUARED_UPPER}])");
if (tauMaxSquared < TAU_MAX_SQUARED_LOWER || tauMaxSquared > TAU_MAX_SQUARED_UPPER)
{
LogError($"τ_max²({tauMaxSquared:F3})不在国标要求范围内");
return InvalidResult($"τ_max²({tauMaxSquared:F3})不在国标要求范围[{TAU_MAX_SQUARED_LOWER}, {TAU_MAX_SQUARED_UPPER}]内,测试无效");
}
LogInfo("τ_max²验证通过");
// 5. 计算导热系数λ - 国标公式(4)
LogInfo("步骤5: 计算导热系数λ - 国标公式(4)");
double lambda = CalculateThermalConductivity(timeArray, deltaT, alpha, tc);
LogInfo($"导热系数计算结果: λ={lambda:F6} W/(m·K)");
// 6. 计算比热容Cp - Cp = λ / (ρ × α)
LogInfo("步骤6: 计算比热容Cp");
double cp = CalculateSpecificHeatCapacity(lambda, alpha);
LogInfo($"比热容计算结果: Cp={cp:F2} J/(kg·K)");
// 7. 验证测试结果有效性
LogInfo("步骤7: 验证测试结果有效性");
var validation = ValidateTestResults(timeArray, alpha, tc, tauMaxSquared);
LogInfo($"测试结果验证: {(validation.IsValid ? "" : "")}");
// 8. 计算R²
LogInfo("步骤8: 计算拟合优度R²");
double rSquared = CalculateRSquared(timeArray, deltaT, alpha, tc, lambda);
LogInfo($"R²计算结果: {rSquared:F6}");
// 生成最终日志
LogInfo("=".PadRight(80, '='));
LogInfo("计算完成");
LogInfo($"最终结果: λ={lambda:F6} W/(m·K), α={alpha:E6} m²/s, Cp={cp:F2} J/(kg·K)");
LogInfo($"校正参数: tc={tc:F4}s, R²={rSquared:F6}");
return new CalculationResult
{
IsValid = validation.IsValid,
ThermalConductivity = lambda,
ThermalDiffusivity = alpha,
SpecificHeatCapacity = cp,
CorrectionTime = tc,
RSquared = rSquared,
ValidationMessage = string.Join("\n", validation.Messages),
CalculationLog = _logBuilder.ToString() // 保存完整日志
};
}
catch (Exception ex)
{
LogError($"计算失败: {ex.Message}");
LogError($"异常堆栈: {ex.StackTrace}");
return InvalidResult($"计算失败: {ex.Message}");
}
}
/// <summary>
/// 计算温度增量ΔT(t) - 国标公式(3)
/// </summary>
private double[] CalculateTemperatureIncrements(double[] deltaUArray, double I0)
{
LogDebug($"开始计算ΔT(t),共{deltaUArray.Length}个点");
double[] deltaT = new double[deltaUArray.Length];
for (int i = 0; i < deltaUArray.Length; i++)
{
double deltaU = deltaUArray[i];
// 国标公式(3): ΔT(t) = (Rs + RL + R0) × ΔU(t) / [(I0 × Rs - ΔU(t)) × α × R0]
double numerator = (_bridge.Rs + _bridge.RL + _probe.R0) * deltaU;
double denominator = (I0 * _bridge.Rs - deltaU) * _probe.Alpha * _probe.R0;
if (Math.Abs(denominator) < 1e-12)
{
LogError($"第{i}点计算ΔT(t)时分母接近0: denominator={denominator:E6}");
throw new InvalidOperationException($"分母接近0无法计算ΔT(t)");
}
deltaT[i] = numerator / denominator;
// 每100个点记录一次进度
if (i % 100 == 0 && i > 0)
{
LogDebug($"已计算{i}个ΔT点当前值={deltaT[i]:E6}K");
}
}
LogDebug($"ΔT(t)计算完成最后10个值: {string.Join(", ", deltaT.Skip(Math.Max(0, deltaT.Length - 10)).Select(d => d.ToString("E3")))}");
return deltaT;
}
/// <summary>
/// 迭代求解热扩散系数α和校正时间tc
/// </summary>
private (double alpha, double tc, string iterationLog) IterateThermalDiffusivity(double[] timeArray, double[] deltaT)
{
StringBuilder iterationLog = new StringBuilder();
iterationLog.AppendLine("迭代过程记录:");
iterationLog.AppendLine("迭代 | α | tc | 误差 | 改进 | 状态");
iterationLog.AppendLine("-".PadRight(80, '-'));
// 初始猜测值
double bestAlpha = GetDefaultAlphaBySoilType(); // 常见建筑材料的热扩散系数
double bestTc = timeArray.Max() * 0.01; // 测试时间的1%作为初始校正时间
double bestError = double.MaxValue;
//bestAlpha = 1e-8;
LogInfo($"迭代初始值: α={bestAlpha:E6} m²/s, tc={bestTc:F4}s");
// 根据土壤类型调整迭代参数
double alphaStepBase = 0.1; // 基础步长比例
double tcStepBase = 0.001; // 基础时间步长
int maxIterations = 500;
double convergenceThreshold = 1e-8;
// 针对干土使用更小的步长
if (_test.cmbSampleType == "干土")
{
alphaStepBase = 0.05; // 干土需要更小的步长
tcStepBase = 0.0005;
}
else if (_test.cmbSampleType == "湿土")
{
alphaStepBase = 0.2; // 干土需要更小的步长
tcStepBase = 0.0005;
}
else if (_test.cmbSampleType == "冻土")
{
alphaStepBase = 0.2; // 冻土可以使用稍大步长
tcStepBase = 0.0005;
}
// 使用最小二乘法迭代优化
maxIterations = 5000; // 增加最大迭代次数
int iteration;
bool converged = false;
for (iteration = 0; iteration < maxIterations; iteration++)
{
//double alphaStep = bestAlpha * 0.1 / (iteration + 1);
//double tcStep = 0.001 / (iteration + 1);
// 使用动态调整的步长
double alphaStep = bestAlpha * alphaStepBase / (iteration + 1);
double tcStep = tcStepBase / (iteration + 1);
var candidates = new List<(double alpha, double tc, double error)>
{
(bestAlpha, bestTc, CalculateFitError(timeArray, deltaT, bestAlpha, bestTc)),
(bestAlpha + alphaStep, bestTc, CalculateFitError(timeArray, deltaT, bestAlpha + alphaStep, bestTc)),
(bestAlpha - alphaStep, bestTc, CalculateFitError(timeArray, deltaT, bestAlpha - alphaStep, bestTc)),
(bestAlpha, bestTc + tcStep, CalculateFitError(timeArray, deltaT, bestAlpha, bestTc + tcStep)),
(bestAlpha, bestTc - tcStep, CalculateFitError(timeArray, deltaT, bestAlpha, bestTc - tcStep))
};
var bestCandidate = candidates.OrderBy(c => c.error).First();
double improvement = bestError - bestCandidate.error;
string status = "搜索";
// 收敛条件
if (Math.Abs(improvement) < convergenceThreshold && iteration > 20)
{
status = "收敛";
converged = true;
}
iterationLog.AppendLine($"{iteration + 1,3} | {bestCandidate.alpha:E6} | {bestCandidate.tc:F6} | {bestCandidate.error:E6} | {improvement:E6} | {status}");
if (improvement > 0)
{
bestAlpha = bestCandidate.alpha;
bestTc = bestCandidate.tc;
bestError = bestCandidate.error;
}
// 每10次迭代记录一次详细状态
if (iteration % 10 == 0)
{
LogDebug($"迭代{iteration}: α={bestAlpha:E6}, tc={bestTc:F6}, 误差={bestError:E6}, 改进={improvement:E6}");
}
// 收敛后停止迭代
if (converged && iteration > 20)
{
LogInfo($"第{iteration + 1}次迭代达到收敛条件,停止迭代");
break;
}
}
iterationLog.AppendLine("-".PadRight(80, '-'));
iterationLog.AppendLine($"总迭代次数: {iteration + 1}次");
iterationLog.AppendLine($"是否收敛: {(converged ? "" : "")}");
iterationLog.AppendLine($"最终误差: {bestError:E6}");
iterationLog.AppendLine($"步长调整: α步长={bestAlpha * 0.1 / (iteration + 1):E6}, tc步长={0.001 / (iteration + 1):E6}");
if (!converged && iteration >= maxIterations)
{
LogWarning($"达到最大迭代次数({maxIterations})仍未收敛");
iterationLog.AppendLine($"警告: 达到最大迭代次数仍未完全收敛");
}
return (bestAlpha, bestTc, iterationLog.ToString());
}
/// <summary>
/// 计算拟合误差 - 使用过原点回归的残差平方和
/// </summary>
private double CalculateFitError(double[] timeArray, double[] deltaT, double alpha, double tc)
{
double r = _probe.RadiusMM / 1000.0;
var dTauList = new List<double>();
var deltaTList = new List<double>();
// 收集有效数据点
for (int i = 0; i < timeArray.Length; i++)
{
if (timeArray[i] > tc)
{
double tau = CalculateTau(timeArray[i], tc, alpha, r);
// 检查τ是否在合理范围内 - 修复:使用正确的τ范围 [√0.3, 1] = [0.5477, 1]
if (tau >= Math.Sqrt(TAU_MAX_SQUARED_LOWER) && tau <= Math.Sqrt(TAU_MAX_SQUARED_UPPER))
{
// 边界检查
if (tau < 0.01 || tau > 3.0) continue;
try
{
double dTau = _tauDInterpolation.Interpolate(tau);
dTauList.Add(dTau);
deltaTList.Add(deltaT[i]);
}
catch (Exception ex)
{
LogDebug($"第{i}点插值失败: τ={tau:F4}, 错误={ex.Message}");
continue;
}
}
}
}
if (dTauList.Count < 10)
{
LogDebug($"误差计算: 有效数据点不足({dTauList.Count}),返回最大误差");
return double.MaxValue;
}
// 过原点线性回归: ΔT = k * D(τ)
double numerator = 0;
double denominator = 0;
for (int i = 0; i < dTauList.Count; i++)
{
numerator += dTauList[i] * deltaTList[i];
denominator += dTauList[i] * dTauList[i];
}
if (Math.Abs(denominator) < 1e-12)
{
LogDebug("误差计算: 分母接近0返回最大误差");
return double.MaxValue;
}
double slope = numerator / denominator; // k = Σ(D*ΔT) / Σ(D²)
// 计算均方根误差 (RMSE)
double totalError = 0;
for (int i = 0; i < dTauList.Count; i++)
{
double predicted = slope * dTauList[i]; // 过原点预测值
double error = deltaTList[i] - predicted;
totalError += error * error;
}
double rmse = Math.Sqrt(totalError / dTauList.Count);
double relativeRmse = rmse / Math.Abs(deltaTList.Average()); // 相对误差
LogDebug($"误差计算: 使用{dTauList.Count}个点, 斜率={slope:E6}, RMSE={rmse:E6}, 相对误差={relativeRmse:P2}");
return relativeRmse; // 返回相对误差
}
/// <summary>
/// 计算导热系数λ - 国标公式(4),使用国标范围内的数据
/// </summary>
private double CalculateThermalConductivity(double[] timeArray, double[] deltaT, double alpha, double tc)
{
LogInfo("开始计算导热系数λ");
double r = _probe.RadiusMM / 1000.0;
var validPoints = new List<(double dTau, double deltaT)>();
// 收集有效数据点(严格按国标τ范围筛选)
int validCount = 0;
int totalCount = 0;
int tGreaterTcCount = 0;
LogDebug("筛选有效数据点用于线性回归:");
for (int i = 0; i < timeArray.Length; i++)
{
totalCount++;
// 只使用校正后的数据t > t_c
if (timeArray[i] > tc)
{
tGreaterTcCount++;
double tau = CalculateTau(timeArray[i], tc, alpha, r);
// 只使用τ在国标要求范围内的数据点
if (tau >= Math.Sqrt(TAU_MAX_SQUARED_LOWER) && tau <= Math.Sqrt(TAU_MAX_SQUARED_UPPER))
{
try
{
double dTau = _tauDInterpolation.Interpolate(tau);
validPoints.Add((dTau, deltaT[i]));
validCount++;
// 每50个有效点记录一次
if (validCount % 50 == 0)
{
LogDebug($"已找到{validCount}个有效点,当前点: t={timeArray[i]:F4}s, τ={tau:F4}, D(τ)={dTau:F6}, ΔT={deltaT[i]:E6}K");
}
}
catch (Exception ex)
{
LogDebug($"第{i}点插值失败: τ={tau:F4}, 错误={ex.Message}");
continue;
}
}
}
}
LogInfo($"数据点统计: 总数={totalCount}, t>tc点数={tGreaterTcCount}, τ有效点数={validCount}");
if (validPoints.Count < 10)
{
LogError($"有效数据点不足({validPoints.Count}),无法计算导热系数");
throw new InvalidOperationException(
$"有效数据点不足({validPoints.Count}),无法计算导热系数\n" +
$"国标要求:在τ范围[{TAU_MAX_SQUARED_LOWER}, {TAU_MAX_SQUARED_UPPER}]内应有足够数据点");
}
LogInfo($"使用{validPoints.Count}个有效数据点进行线性回归");
// 线性回归ΔT(τ) = [P₀ / (π^(3/2) × r × λ)] × D(τ)
double[] dTauArray = validPoints.Select(p => p.dTau).ToArray();
double[] deltaTArray = validPoints.Select(p => p.deltaT).ToArray();
LogDebug($"D(τ)范围: [{dTauArray.Min():F6}, {dTauArray.Max():F6}]");
LogDebug($"ΔT范围: [{deltaTArray.Min():E6}, {deltaTArray.Max():E6}]");
var (slope, intercept) = LinearRegression(dTauArray, deltaTArray, true);
LogInfo($"线性回归结果: 斜率={slope:E6}, 截距={intercept:E6}, R={CalculateCorrelation(dTauArray, deltaTArray):F6}");
// 检查斜率是否合理
if (Math.Abs(slope) < 1e-12)
{
LogError($"回归斜率接近0 ({slope:E6}),计算结果不可靠");
throw new InvalidOperationException("回归斜率接近0计算结果不可靠");
}
// 计算导热系数 λ = P₀ / (π^(3/2) × r × slope) - 国标公式(4)
double lambda = _test.P0 / (PI_POW_1_5 * r * slope);
LogInfo($"导热系数计算: P0={_test.P0}W, r={r}m, π^(3/2)={PI_POW_1_5:F6}, slope={slope:E6}");
LogInfo($"最终导热系数: λ={lambda:F6} W/(m·K)");
return lambda;
}
/// <summary>
/// 计算无量纲时间τ - 国标公式(5)
/// τ = √[(t - t_c) / (r²/a)]
/// 等价于:τ = √[(t - t_c) * a / r²]
/// </summary>
private double CalculateTau(double time, double tc, double alpha, double r)
{
if (time <= tc)
{
return 0;
}
// 国标公式(5): τ = √[(t - t_c) / (r²/a)]
double theta = (r * r) / alpha; // 特征时间 θ = r²/a
double tau = Math.Sqrt((time - tc) / theta);
return tau;
}
/// <summary>
/// 计算比热容Cp - Cp = λ / (ρ × α)
/// </summary>
private double CalculateSpecificHeatCapacity(double lambda, double alpha)
{
LogInfo($"开始计算比热容: λ={lambda:F6}, α={alpha:E6}, ρ={_test.SampleDensity}");
if (alpha <= 0 || _test.SampleDensity <= 0)
{
LogError($"参数无效: α={alpha:E6}, ρ={_test.SampleDensity}");
throw new InvalidOperationException("热扩散系数或密度无效,无法计算比热容");
}
double cp = lambda / (_test.SampleDensity * alpha);
LogInfo($"比热容计算: Cp = {lambda:F6} / ({_test.SampleDensity} × {alpha:E6}) = {cp:F2} J/(kg·K)");
return cp;
}
/// <summary>
/// 验证测试结果 - 严格遵循国标5.3.4节要求
/// </summary>
private ValidationResult ValidateTestResults(double[] timeArray, double alpha, double tc, double tauMaxSquared)
{
LogInfo("开始测试结果验证");
var result = new ValidationResult { IsValid = true };
double r = _probe.RadiusMM / 1000.0; // 转换为米
double tmax = timeArray.Max();
// 1. 验证τ_max²是否满足国标要求
if (tauMaxSquared < TAU_MAX_SQUARED_LOWER)
{
result.IsValid = false;
string msg = $"τ_max²({tauMaxSquared:F3}) < {TAU_MAX_SQUARED_LOWER},测试时间不足,结果无效";
LogError(msg);
result.Messages.Add(msg);
}
else if (tauMaxSquared > TAU_MAX_SQUARED_UPPER)
{
result.IsValid = false;
string msg = $"τ_max²({tauMaxSquared:F3}) > {TAU_MAX_SQUARED_UPPER},测试时间过长,结果无效";
LogError(msg);
result.Messages.Add(msg);
}
else
{
string msg = $"τ_max²({tauMaxSquared:F3})满足国标要求[{TAU_MAX_SQUARED_LOWER}, {TAU_MAX_SQUARED_UPPER}]";
LogInfo(msg);
result.Messages.Add(msg);
}
// 2. 计算探测深度 ΔP_probe = 2√(a·t_max) 国标5.3.4
double probingDepth = 2 * Math.Sqrt(alpha * tmax);
double probingDepthRatio = probingDepth / r;
LogInfo($"探测深度计算: 2×√({alpha:E6}×{tmax:F2}) = {probingDepth * 1000:F2}mm, 探头半径={r * 1000:F2}mm, 比值={probingDepthRatio:F2}");
// 3. 验证探测深度是否满足国标要求
if (probingDepthRatio < PROBING_DEPTH_RATIO_LOWER)
{
result.IsValid = false;
string msg = $"探测深度/探头半径({probingDepthRatio:F2}) < {PROBING_DEPTH_RATIO_LOWER},样品厚度可能不足";
LogError(msg);
result.Messages.Add(msg);
}
else if (probingDepthRatio > PROBING_DEPTH_RATIO_UPPER)
{
result.IsValid = false;
string msg = $"探测深度/探头半径({probingDepthRatio:F2}) > {PROBING_DEPTH_RATIO_UPPER},可能超出测试范围";
LogError(msg);
result.Messages.Add(msg);
}
else
{
string msg = $"探测深度/探头半径({probingDepthRatio:F2})满足国标要求[{PROBING_DEPTH_RATIO_LOWER}, {PROBING_DEPTH_RATIO_UPPER}]";
LogInfo(msg);
result.Messages.Add(msg);
}
// 4. 验证数据点数量国标5.3.3.3要求采集次数大于100次
if (timeArray.Length < MIN_DATA_COUNT)
{
string msg = $"警告:采集次数({timeArray.Length})少于国标要求的{MIN_DATA_COUNT}次";
LogWarning(msg);
result.Messages.Add(msg);
}
else
{
string msg = $"采集次数({timeArray.Length})满足国标要求(≥{MIN_DATA_COUNT}次)";
LogInfo(msg);
result.Messages.Add(msg);
}
// 5. 验证数据采集间隔国标5.3.3.3要求不小于0.1s
if (timeArray.Length > 1)
{
double minInterval = timeArray[1] - timeArray[0];
for (int i = 2; i < timeArray.Length; i++)
{
double interval = timeArray[i] - timeArray[i - 1];
if (interval < minInterval) minInterval = interval;
}
if (minInterval < MIN_TIME_INTERVAL)
{
string msg = $"警告:数据采集间隔({minInterval:F3}s)小于国标要求的{MIN_TIME_INTERVAL}s";
LogWarning(msg);
result.Messages.Add(msg);
}
else
{
string msg = $"数据采集间隔({minInterval:F3}s)满足国标要求(≥{MIN_TIME_INTERVAL}s)";
LogInfo(msg);
result.Messages.Add(msg);
}
}
// 6. 生成详细验证报告
result.Messages.Insert(0, $"【国标GB/T 32064-2015测试结果有效性验证】");
result.Messages.Insert(1, $"测试总时间: {tmax:F2}s校正时间: {tc:F4}s");
result.Messages.Insert(2, $"热扩散系数α: {alpha:E6} m²/s");
result.Messages.Insert(3, $"探头半径r: {r * 1000:F2}mm探测深度: {probingDepth * 1000:F2}mm");
result.Messages.Insert(4, $"验证依据5.3.4节 测试结果有效性验证");
LogInfo($"测试结果验证完成: {(result.IsValid ? "" : "")}");
return result;
}
/// <summary>
/// 计算τ_max²
/// </summary>
private double CalculateTauMaxSquared(double tmax, double tc, double alpha, double r)
{
LogDebug($"计算τ_max²: tmax={tmax:F4}, tc={tc:F4}, α={alpha:E6}, r={r:F6}");
// 国标公式τ_max² = (t_max - t_c) × a / r²
if (r <= 0) throw new ArgumentException("探头半径必须大于0");
if (tmax <= tc)
{
LogWarning($"tmax({tmax:F4}) <= tc({tc:F4})返回0");
return 0;
}
//double tauMaxSquared = (tmax - tc) * alpha / (r * r);
double tauMax = Math.Sqrt((tmax - tc) * alpha / (r * r));
double tauMaxSquared = tauMax * tauMax; // τ² = τ×τ
LogDebug($"τ_max²计算结果: ({tmax}-{tc})×{alpha:E6}/{r * r:E6} = {tauMaxSquared:F4}");
return tauMaxSquared;
}
/// <summary>
/// 计算R²拟合优度
/// </summary>
private double CalculateRSquared(double[] timeArray, double[] deltaT, double alpha, double tc, double lambda)
{
LogInfo("开始计算R²拟合优度");
var observed = new List<double>();
var predicted = new List<double>();
double r = _probe.RadiusMM / 1000.0;
int validCount = 0;
for (int i = 0; i < timeArray.Length; i++)
{
if (timeArray[i] > tc)
{
double tau = CalculateTau(timeArray[i], tc, alpha, r);
//if (tau >= TAU_MAX_SQUARED_LOWER && tau <= TAU_MAX_SQUARED_UPPER)
double tauSquared = tau * tau; // 计算τ²
// 修复:使用τ²的范围进行判断
if (tauSquared >= TAU_MAX_SQUARED_LOWER && tauSquared <= TAU_MAX_SQUARED_UPPER)
{
try
{
double dTau = _tauDInterpolation.Interpolate(tau);
double theoretical = _test.P0 * dTau / (PI_POW_1_5 * r * lambda);
observed.Add(deltaT[i]);
predicted.Add(theoretical);
validCount++;
}
catch
{
continue;
}
}
}
}
LogInfo($"R²计算使用{validCount}个有效数据点");
if (observed.Count < 10)
{
LogWarning($"R²计算数据点不足({observed.Count})返回0");
return 0;
}
double meanObserved = observed.Average();
double ssTotal = observed.Sum(o => Math.Pow(o - meanObserved, 2));
double ssResidual = observed.Zip(predicted, (o, p) => Math.Pow(o - p, 2)).Sum();
if (ssResidual > ssTotal)
{
LogWarning($"SSR({ssResidual:E6}) > SST({ssTotal:E6})强制设为0");
return 0;
}
LogDebug($"R²计算: 平均值={meanObserved:E6}, SST={ssTotal:E6}, SSR={ssResidual:E6}");
if (Math.Abs(ssTotal) < 1e-12)
{
LogWarning("SST接近0返回0");
return 0;
}
double rSquared = 1 - (ssResidual / ssTotal);
LogInfo($"R²计算结果: {rSquared:F6} (SST={ssTotal:E6}, SSR={ssResidual:E6})");
return rSquared;
}
#endregion
#region
private CalculationResult InvalidResult(string message)
{
LogError($"返回无效结果: {message}");
return new CalculationResult
{
IsValid = false,
ValidationMessage = message,
CalculationLog = _logBuilder.ToString(),
ThermalConductivity = 0,
ThermalDiffusivity = 0,
SpecificHeatCapacity = 0,
CorrectionTime = 0,
RSquared = 0
};
}
private bool ValidateInputData(double[] timeArray, double[] deltaUArray)
{
LogDebug("开始验证输入数据");
if (timeArray == null || deltaUArray == null)
{
LogError("时间和电压数据不能为空");
throw new ArgumentNullException("时间和电压数据不能为空");
}
if (timeArray.Length != deltaUArray.Length)
{
LogError($"数据长度不一致: 时间{timeArray.Length}点, 电压{deltaUArray.Length}点");
throw new ArgumentException("时间和电压数据长度不一致");
}
if (timeArray.Length < 20)
{
LogError($"数据点数量({timeArray.Length})不足至少需要20个点");
throw new ArgumentException($"数据点数量({timeArray.Length})不足至少需要20个点");
}
// 检查时间单调递增
for (int i = 1; i < timeArray.Length; i++)
{
if (timeArray[i] <= timeArray[i - 1])
{
LogError($"时间数据非单调递增: 第{i - 1}点={timeArray[i - 1]}, 第{i}点={timeArray[i]}");
throw new ArgumentException($"时间数据必须严格单调递增,第{i}点不符合要求");
}
}
// 统计信息
double timeSpan = timeArray[timeArray.Length - 1] - timeArray[0];
double avgInterval = timeSpan / (timeArray.Length - 1);
LogInfo($"数据验证通过: 时间范围[{timeArray[0]:F4}s, {timeArray[timeArray.Length - 1]:F4}s], 时长={timeSpan:F2}s");
LogInfo($"数据统计: 点数={timeArray.Length}, 平均间隔={avgInterval:F4}s");
LogInfo($"电压范围: [{deltaUArray.Min():E6}V, {deltaUArray.Max():E6}V], 平均值={deltaUArray.Average():E6}V");
return true;
}
private (double slope, double intercept) LinearRegression(double[] x, double[] y, bool forceOrigin = true)
{
LogDebug($"线性回归: 输入{x.Length}个点");
if (x.Length != y.Length || x.Length < 2)
{
LogError($"线性回归参数错误: x长度={x.Length}, y长度={y.Length}");
throw new ArgumentException("线性回归需要至少2个数据点且x、y长度相等");
}
if (forceOrigin)
{
// 过原点回归y = kx
double numerator = 0;
double denominator = 0;
for (int i = 0; i < x.Length; i++)
{
numerator += x[i] * y[i];
denominator += x[i] * x[i];
}
if (Math.Abs(denominator) < 1e-12)
{
LogError("数据方差太小,无法进行线性回归");
throw new InvalidOperationException("数据方差太小,无法进行线性回归");
}
double slope = numerator / denominator;
double intercept = 0;
LogDebug($"过原点回归: 斜率={slope:E6}");
return (slope, intercept);
}
else
{
double xAvg = x.Average();
double yAvg = y.Average();
LogDebug($"平均值: x̄={xAvg:E6}, ȳ={yAvg:E6}");
double numerator = 0;
double denominator = 0;
for (int i = 0; i < x.Length; i++)
{
double xDiff = x[i] - xAvg;
double yDiff = y[i] - yAvg;
numerator += xDiff * yDiff;
denominator += xDiff * xDiff;
}
LogDebug($"回归计算: 分子={numerator:E6}, 分母={denominator:E6}");
if (Math.Abs(denominator) < 1e-12)
{
LogError("数据方差太小,无法进行线性回归");
throw new InvalidOperationException("数据方差太小,无法进行线性回归");
}
double slope = numerator / denominator;
double intercept = yAvg - slope * xAvg;
LogDebug($"回归结果: 斜率={slope:E6}, 截距={intercept:E6}");
return (slope, intercept);
}
}
/// <summary>
/// 计算相关系数
/// </summary>
private double CalculateCorrelation(double[] x, double[] y)
{
if (x.Length != y.Length || x.Length < 2) return 0;
double xAvg = x.Average();
double yAvg = y.Average();
double numerator = 0;
double xSumSq = 0;
double ySumSq = 0;
for (int i = 0; i < x.Length; i++)
{
double xDiff = x[i] - xAvg;
double yDiff = y[i] - yAvg;
numerator += xDiff * yDiff;
xSumSq += xDiff * xDiff;
ySumSq += yDiff * yDiff;
}
if (xSumSq == 0 || ySumSq == 0) return 0;
return numerator / Math.Sqrt(xSumSq * ySumSq);
}
#endregion
#region
private void LogInfo(string message)
{
string logEntry = $"[INFO] {DateTime.Now:HH:mm:ss.fff} - {message}";
_logBuilder.AppendLine(logEntry);
// 这里也可以输出到控制台或文件
// Console.WriteLine(logEntry);
}
private void LogDebug(string message)
{
string logEntry = $"[DEBUG] {DateTime.Now:HH:mm:ss.fff} - {message}";
_logBuilder.AppendLine(logEntry);
// 调试信息可根据需要启用或禁用
// Console.WriteLine(logEntry);
}
private void LogWarning(string message)
{
string logEntry = $"[WARNING] {DateTime.Now:HH:mm:ss.fff} - {message}";
_logBuilder.AppendLine(logEntry);
// Console.WriteLine(logEntry);
}
private void LogError(string message)
{
string logEntry = $"[ERROR] {DateTime.Now:HH:mm:ss.fff} - {message}";
_logBuilder.AppendLine(logEntry);
// Console.WriteLine(logEntry);
}
#endregion
#region τ-D(τ)
private void InitializeTauDTable()
{
LogInfo("初始化τ-D(τ)插值表");
// 根据国标GB/T 32064-2015瞬态平面热源法的D(τ)函数
// D(τ) = [√(τ²+1) - 1] / τ 或 D(τ) = [√(τ²+1) - τ] / [√(τ²+1) + τ]
// 需要确认哪个是正确的公式!
var tauList = new List<double>();
var dList = new List<double>();
LogDebug("生成τ-D(τ)数据点");
// 先计算并记录两种公式的差异
LogDebug("τ-D(τ)公式验证:");
for (double tau = 0.5; tau <= 2.5; tau += 0.5)
{
double d1 = (Math.Sqrt(tau * tau + 1.0) - 1.0) / tau;
double d2 = (Math.Sqrt(tau * tau + 1.0) - tau) / (Math.Sqrt(tau * tau + 1.0) + tau);
LogDebug($"τ={tau:F2}: 公式1(D1)={d1:F6}, 公式2(D2)={d2:F6}, 比值={d1 / d2:F3}");
}
for (double tau = 0.01; tau <= 3.0; tau += 0.01)
{
tauList.Add(tau);
double d = (Math.Sqrt(tau * tau + 1.0) - 1.0) / tau;
dList.Add(d);
}
double[] tauValues = tauList.ToArray();
double[] dValues = dList.ToArray();
_tauDInterpolation = CubicSpline.InterpolateAkimaSorted(tauValues, dValues);
LogInfo($"τ-D(τ)插值表初始化完成,共{tauValues.Length}个点,τ范围[{tauValues[0]:F2}, {tauValues[tauValues.Length - 1]:F2}]");
}
//private void InitializeTauDTable()
//{
// LogInfo("初始化τ-D(τ)插值表");
// // 根据国标GB/T 32064-2015瞬态平面热源法的D(τ)函数
// // D(τ) = 1 / [1 + 1/(4τ) + 1/(2(4τ)²) - ...] 的近似公式
// var tauList = new List<double>();
// var dList = new List<double>();
// LogDebug("生成τ-D(τ)数据点");
// // 先计算并记录两种公式的差异
// LogDebug("τ-D(τ)公式验证:");
// for (double tau = 0.5; tau <= 2.5; tau += 0.5)
// {
// // 修正后的正确公式
// double d1 = CalculateDTauCorrected(tau);
// double d2 = (Math.Sqrt(tau * tau + 1.0) - tau) / (Math.Sqrt(tau * tau + 1.0) + tau);
// LogDebug($"τ={tau:F2}: 正确公式(D1)={d1:F6}, 原公式(D2)={d2:F6}, 比值={d1 / d2:F3}");
// }
// for (double tau = 0.01; tau <= 3.0; tau += 0.01)
// {
// tauList.Add(tau);
// // 修改这里:使用正确的公式
// double d = CalculateDTauCorrected(tau);
// dList.Add(d);
// }
// double[] tauValues = tauList.ToArray();
// double[] dValues = dList.ToArray();
// _tauDInterpolation = CubicSpline.InterpolateAkimaSorted(tauValues, dValues);
// LogInfo($"τ-D(τ)插值表初始化完成,共{tauValues.Length}个点,τ范围[{tauValues[0]:F2}, {tauValues[tauValues.Length - 1]:F2}]");
//}
// 新增辅助方法计算正确的D(τ)函数
private double CalculateDTauCorrected(double tau)
{
if (tau <= 0.01)
{
// 当τ很小时的近似D(τ) ≈ 4τ/π
return 4 * tau / Math.PI;
}
else
{
// 国标GB/T 32064-2015中的正确近似公式
// D(τ) = 1 / [1 + 1/(4τ)]
return 1.0 / (1.0 + 1.0 / (4 * tau));
// 如果需要更高精度,可以使用:
// D(τ) = 1 / [1 + 1/(4τ) + 1/(2*(4τ)²) - 0.362/(4τ)³ + 0.116/(4τ)⁴]
// 但对于您的湿土测试,上面的简单公式已经足够
}
}
#endregion
#region
private class ValidationResult
{
public bool IsValid { get; set; }
public List<string> Messages { get; } = new List<string>();
}
#endregion
}
}