Files
L007/GB32064Calculator - 副本良好,密度1780导热系数0.55.cs
2026-01-08 21:12:15 +08:00

1269 lines
53 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 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
case "湿土":
return 2.0e-6; // 湿土典型值
case "冻土":
return 2.5e-6; // 冻土典型值
default:
return 1.6e-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;
LogInfo($"迭代初始值: α={bestAlpha:E6} m²/s, tc={bestTc:F4}s");
// 使用最小二乘法迭代优化
int maxIterations = 500;
double convergenceThreshold = GetDefaultAlphaBySoilType();
convergenceThreshold = 1e-8;
int iteration;
bool converged = false;
for (iteration = 0; iteration < maxIterations; iteration++)
{
double alphaStep = bestAlpha * 0.1 / (iteration + 1);
double tcStep = 0.001 / (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());
}
//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 r = _probe.RadiusMM / 1000.0;
// double tmax = timeArray.Max();
// // 基于τ²=0.8估算初始α(之前可能估算偏小)
// double initialAlpha = 0.8 * r * r / (tmax * 0.99); // τ²=0.8假设tc≈0
// // 关键修改强制初始α不小于1e-6
// initialAlpha = Math.Max(initialAlpha, 2.0e-6);
// initialAlpha = Math.Min(initialAlpha, 3.0e-6);
// double bestAlpha = initialAlpha;
// double bestTc = tmax * 0.01;
// double bestError = CalculateFitError(timeArray, deltaT, bestAlpha, bestTc);
// LogInfo($"强制初始α: α={bestAlpha:E6} m²/s (基于τ²=0.8估算), tc={bestTc:F4}s");
// int maxIterations = 200;
// double convergenceThreshold = 1e-8;
// int iteration;
// bool converged = false;
// // 记录最佳解的改进历史
// List<double> errorHistory = new List<double> { bestError };
// for (iteration = 0; iteration < maxIterations; iteration++)
// {
// // 动态步长:初期探索大,后期精细调整
// double alphaStep = bestAlpha * (0.3 / (1 + iteration * 0.05)); // 衰减更慢
// double tcStep = 0.01 / (1 + iteration * 0.05);
// // 确保最小步长
// alphaStep = Math.Max(alphaStep, bestAlpha * 0.05);
// tcStep = Math.Max(tcStep, 0.001);
// // 生成候选解
// var candidates = new List<(double alpha, double tc, double error)>
//{
// (bestAlpha, bestTc, bestError),
// (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)),
// // 强制探索更大的α值
// (bestAlpha * 1.5, bestTc, CalculateFitError(timeArray, deltaT, bestAlpha * 1.5, bestTc)),
// (bestAlpha * 0.67, bestTc, CalculateFitError(timeArray, deltaT, bestAlpha * 0.67, bestTc)),
//};
// var bestCandidate = candidates.OrderBy(c => c.error).First();
// double improvement = bestError - bestCandidate.error;
// string status = "搜索";
// // 收敛条件连续5次改进小于阈值
// errorHistory.Add(bestCandidate.error);
// if (errorHistory.Count > 5)
// {
// double recentImprovement = errorHistory[errorHistory.Count - 6] - errorHistory[errorHistory.Count - 1];
// if (Math.Abs(recentImprovement) < convergenceThreshold * 5 && 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 || (improvement == 0 && bestCandidate.error < bestError))
// {
// bestAlpha = bestCandidate.alpha;
// bestTc = bestCandidate.tc;
// bestError = bestCandidate.error;
// }
// // 每10次迭代记录一次
// if (iteration % 10 == 0)
// {
// LogDebug($"迭代{iteration}: α={bestAlpha:E6}, tc={bestTc:F6}, 误差={bestError:E6}");
// // 如果连续多次没有改进,尝试跳跃
// if (iteration > 30 && errorHistory.Count > 10)
// {
// double lastImprovement = errorHistory[errorHistory.Count - 11] - errorHistory[errorHistory.Count - 1];
// if (Math.Abs(lastImprovement) < 1e-6)
// {
// // 随机扰动
// double randomFactor = 0.8 + (new Random().NextDouble() * 0.4);
// bestAlpha *= randomFactor;
// LogDebug($"迭代{iteration}: 停滞检测,随机扰动α乘以{randomFactor:F3}");
// }
// }
// }
// if (converged && iteration > 20)
// {
// LogInfo($"第{iteration + 1}次迭代达到收敛条件");
// break;
// }
// }
// iterationLog.AppendLine("-".PadRight(80, '-'));
// iterationLog.AppendLine($"总迭代次数: {iteration + 1}次");
// iterationLog.AppendLine($"是否收敛: {(converged ? "是" : "否")}");
// iterationLog.AppendLine($"最终误差: {bestError:E6}");
// // 验证α是否合理
// if (bestAlpha < 5e-7)
// {
// LogWarning($"警告:热扩散系数α={bestAlpha:E6}可能过小!建议:");
// LogWarning($" 1. 检查样品是否真的导热很差");
// LogWarning($" 2. 检查探头参数R0、α是否正确");
// LogWarning($" 3. 尝试增加测试时间以获得更大的τ²");
// }
// 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>
///// 计算拟合误差 - 增加对α变化的敏感性
///// </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 { continue; }
// }
// }
// }
// if (dTauList.Count < 10)
// {
// return double.MaxValue; // 返回最大误差
// }
// // 过原点线性回归
// double numerator = 0, 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) return double.MaxValue;
// double slope = numerator / denominator;
// // 关键修改:增加惩罚项,鼓励α在合理范围内
// double rmse = 0;
// for (int i = 0; i < dTauList.Count; i++)
// {
// double predicted = slope * dTauList[i];
// double error = deltaTList[i] - predicted;
// rmse += error * error;
// }
// rmse = Math.Sqrt(rmse / dTauList.Count);
// // 修改1α过小的情况增加惩罚
// double alphaPenalty = 0;
// if (alpha < 5e-7) // 如果α小于0.5e-6增加惩罚
// {
// alphaPenalty = (5e-7 - alpha) * 100; // 惩罚系数
// }
// // 修改2对斜率合理性增加检查根据物理意义
// double expectedSlopeMin = _test.P0 / (PI_POW_1_5 * r * 3.0); // λ=3时的最小斜率
// double expectedSlopeMax = _test.P0 / (PI_POW_1_5 * r * 0.1); // λ=0.1时的最大斜率
// double slopePenalty = 0;
// if (slope < expectedSlopeMin || slope > expectedSlopeMax)
// {
// slopePenalty = 1.0; // 显著惩罚
// }
// // 综合误差 = RMSE + 惩罚项
// double totalError = rmse + alphaPenalty + slopePenalty;
// LogDebug($"误差计算: α={alpha:E6}, 使用{dTauList.Count}点, RMSE={rmse:E3}, α惩罚={alphaPenalty:E3}, 斜率惩罚={slopePenalty:E3}, 总误差={totalError:E3}");
// return totalError;
//}
/// <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)
{
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}]");
}
#endregion
#region
private class ValidationResult
{
public bool IsValid { get; set; }
public List<string> Messages { get; } = new List<string>();
}
#endregion
#region
/// <summary>
/// 用于土壤测试的参数设置建议(仅建议,不影响计算)
/// </summary>
public static class SoilTestRecommendations
{
/// <summary>
/// 根据土壤类型获取建议的测试参数
/// </summary>
/// <param name="soilType">土壤类型(干土、湿土、冻土等)</param>
/// <returns>建议的测试时间范围</returns>
public static (double minTime, double maxTime) GetRecommendedTestTime(string soilType)
{
// 土壤热扩散系数典型范围1e-7 到 1e-6 m²/s
// 使用典型探头半径6.4mm计算
double r = 0.0064; // 米
// 根据τ_max² = (t * a) / r²反推时间
// t = τ_max² * r² / a
// 对于导热系数较低的土壤(干土、冻土)
if (soilType.Contains("冻土") || soilType.Contains("干土"))
{
double alpha = 0.5e-6; // 较低的热扩散系数
double t_min = TAU_MAX_SQUARED_LOWER * r * r / alpha;
double t_max = TAU_MAX_SQUARED_UPPER * r * r / alpha;
return (t_min, t_max);
}
// 对于导热系数较高的土壤(湿土)
else if (soilType.Contains("湿土"))
{
double alpha = 1.2e-6; // 较高的热扩散系数
double t_min = TAU_MAX_SQUARED_LOWER * r * r / alpha;
double t_max = TAU_MAX_SQUARED_UPPER * r * r / alpha;
return (t_min, t_max);
}
// 默认值
else
{
double alpha = 1e-6; // 典型热扩散系数
double t_min = TAU_MAX_SQUARED_LOWER * r * r / alpha;
double t_max = TAU_MAX_SQUARED_UPPER * r * r / alpha;
return (t_min, t_max);
}
}
}
#endregion
}
}