using CapMachine.Core; using CapMachine.Wpf.PPCalculation; using System; namespace CapMachine.Wpf.Services { /// /// 过热度 / 过冷度联合计算的输入数据。 /// /// /// 该输入对象只承载计算所需的原始测点数据,不负责任何单位换算或有效性判断。 /// 计算时各字段的单位约定如下: /// - :吸气压力,单位 BarA(绝压) /// - :吸气温度,单位 ℃ /// - :膨胀阀前液路压力,单位 BarA(绝压) /// - :膨胀阀前液路温度,单位 ℃ /// public sealed class PPCSuperheatSubcoolCalculationInput { /// /// 吸气压力,单位 BarA(绝压)。 /// public double SuctionPressureBarA { get; set; } /// /// 吸气温度,单位 ℃。 /// public double SuctionTemperatureC { get; set; } /// /// 液路压力(膨胀阀前压力),单位 BarA(绝压)。 /// public double LiquidPressureBarA { get; set; } /// /// 液路温度(膨胀阀前温度),单位 ℃。 /// public double LiquidTemperatureC { get; set; } } /// /// 过热度 / 过冷度联合计算结果。 /// /// /// 本类仅承载计算后的结果值,默认初始化为 , /// 以便调用方区分“尚未赋值”和“计算结果为有效数值”两种状态。 /// public sealed class PPCSuperheatSubcoolCalculationResult { /// /// 过热度结果,单位 K。 /// 数值公式:吸气实际温度 - 吸气压力对应饱和温度。 /// public double SuperheatK { get; set; } = double.NaN; /// /// 过冷度结果,单位 K。 /// 数值公式:液路实际温度 - 液路压力对应饱和温度。 /// public double SubcoolK { get; set; } = double.NaN; } /// /// 过热度 / 过冷度独立计算类。 /// /// /// 该类只负责过热度和过冷度的数学/物性计算本身,不直接访问标签,也不负责写回 UI 或实时数据。 /// 为避免不同计算类共享同一套底层实现后在后续维护中相互影响, /// 本类将自身所需的 REFPROP 初始化与饱和温度查询过程内聚在类内私有 support 中。 /// /// 当前实现保持与原有 PPCService 中的流程一致: /// 1. 压力由 BarA 换算到 MPa /// 2. 通过 SATP 按压力求饱和温度 Tsat /// 3. 将 REFPROP 返回的 K 温度换算回 ℃ /// 4. 用“实测温度 - 饱和温度”得到过热度/过冷度 /// public sealed class PPCSuperheatSubcoolCalculator { /// /// 底层物性计算支持对象。 /// 提供 REFPROP 初始化、SATP 饱和性质查询等公共能力。 /// private readonly LocalCalculationSupport _support; /// /// 初始化过热度 / 过冷度计算类。 /// public PPCSuperheatSubcoolCalculator() { _support = new LocalCalculationSupport(); } /// /// 一次性计算过热度和过冷度。 /// /// 输入测点数据,包含吸气侧和液路侧压力/温度。 /// 输出计算结果对象,成功时包含过热度与过冷度。 /// 失败原因。任一子步骤失败时返回具体错误描述。 /// /// - :过热度和过冷度都计算成功 /// - :任一结果计算失败 /// public bool TryCalculate(PPCSuperheatSubcoolCalculationInput input, out PPCSuperheatSubcoolCalculationResult result, out string error) { // 先创建结果对象,并将错误文本置空,保持 TryXXX 风格的一致性。 result = new PPCSuperheatSubcoolCalculationResult(); error = string.Empty; // 先算过热度。 // 只要过热度失败,就直接结束本次联合计算,并把失败原因透传给调用方。 if (!TryCalculateSuperheatK(input.SuctionPressureBarA, input.SuctionTemperatureC, out var superheatK, out var superheatErr)) { error = superheatErr; return false; } // 再算过冷度。 // 保持与原逻辑一致:两个结果都必须成功,联合计算才算成功。 if (!TryCalculateSubcoolK(input.LiquidPressureBarA, input.LiquidTemperatureC, out var subcoolK, out var subcoolErr)) { error = subcoolErr; return false; } // 两个结果均成功后,写入输出对象。 result.SuperheatK = superheatK; result.SubcoolK = subcoolK; return true; } /// /// 计算过热度,单位 K。 /// /// 吸气压力,单位 BarA(绝压)。 /// 吸气温度,单位 ℃。 /// 过热度输出,单位 K。 /// 失败原因,如 REFPROP 未初始化、SATP 查询失败或结果异常。 /// 是否计算成功。 /// /// 计算过程保持与原 PPCService 一致: /// 1. 吸气压力由 BarA 转为 MPa /// 2. 用 SATP 按压力求出该压力下的饱和温度 Tsat(K) /// 3. 把饱和温度换算为 ℃ /// 4. 过热度 = 吸气实测温度 - 饱和温度 /// public bool TryCalculateSuperheatK(double suctionPressureBarA, double suctionTemperatureC, out double superheatK, out string error) { // 默认输出 NaN,表示当前尚未得到有效结果。 superheatK = double.NaN; error = string.Empty; // REFPROP 相关函数调用前先做幂等初始化。 if (!_support.EnsureRefpropInitialized(out var initErr)) { error = initErr; return false; } // 现有物性 helper 约定输入压力为 MPa,因此这里把 BarA 转成 MPa。 double pressureMPa = suctionPressureBarA * 0.1; // 按压力求饱和温度。 // 本计算只关心 Tsat,因此 Dl/Dv 结果不使用,用 out _ 丢弃。 if (!_support.TrySATP_SaturationByP_MPa(pressureMPa, out var tSatK, out _, out _, out var satErr)) { error = satErr; return false; } // REFPROP 返回的饱和温度是 K,这里换算成 ℃后再与实测温度做差。 // 公式与原实现完全一致:Superheat = T_actual - T_sat。 superheatK = suctionTemperatureC - (tSatK - 273.15); // 防御性检查:避免把 NaN / Infinity 写回上层标签。 if (double.IsNaN(superheatK) || double.IsInfinity(superheatK)) { error = "过热度结果异常"; return false; } return true; } /// /// 计算过冷度,单位 K。 /// /// 液路压力(膨胀阀前压力),单位 BarA(绝压)。 /// 液路温度(膨胀阀前温度),单位 ℃。 /// 过冷度输出,单位 K。 /// 失败原因,如 REFPROP 未初始化、SATP 查询失败或结果异常。 /// 是否计算成功。 /// /// 当前实现与原有逻辑保持一致,公式写法为: /// Subcool = T_actual - T_sat /// 即“液路实测温度减去对应压力下的饱和温度”。 /// public bool TryCalculateSubcoolK(double liquidPressureBarA, double liquidTemperatureC, out double subcoolK, out string error) { // 默认输出 NaN,表示当前尚未得到有效结果。 subcoolK = double.NaN; error = string.Empty; // REFPROP 相关函数调用前先做幂等初始化。 if (!_support.EnsureRefpropInitialized(out var initErr)) { error = initErr; return false; } // 现有 SATP helper 约定压力单位为 MPa,因此先由 BarA 转成 MPa。 double pressureMPa = liquidPressureBarA * 0.1; // 查询该液路压力下的饱和温度 Tsat。 if (!_support.TrySATP_SaturationByP_MPa(pressureMPa, out var tSatK, out _, out _, out var satErr)) { error = satErr; return false; } // 与原实现保持一致:直接用实测液路温度减去饱和温度。 subcoolK = liquidTemperatureC - (tSatK - 273.15); // 防御性检查:避免异常数值继续上传。 if (double.IsNaN(subcoolK) || double.IsInfinity(subcoolK)) { error = "过冷度结果异常"; return false; } return true; } /// /// 过热度 / 过冷度计算类私有的底层物性支持实现。 /// /// /// 该实现只服务当前计算类,目的是把本类依赖的 REFPROP 调用过程固定在本文件内部, /// 避免后续为了其他计算类调整共享 support 时,影响已经验算通过的过热度 / 过冷度结果。 /// private sealed class LocalCalculationSupport : IPPCCalculationSupport { private static readonly object _refpropLock = new object(); private static volatile bool _rpInitialized; public bool EnsureRefpropInitialized(out string error) { error = string.Empty; if (_rpInitialized) { return true; } try { lock (_refpropLock) { if (_rpInitialized) { return true; } string hpath = ConfigHelper.GetValue("FluidsPath"); if (string.IsNullOrWhiteSpace(hpath)) { hpath = @".\PPCalculation\REFPROP\FLUIDS"; } string configuredCryogen = ConfigHelper.GetValue("Cryogen"); if (string.IsNullOrWhiteSpace(configuredCryogen)) { configuredCryogen = "R134a"; } string hfldCore = configuredCryogen.Equals("R134a", StringComparison.OrdinalIgnoreCase) ? "R134A.FLD" : "R134A.FLD"; long size = hpath.Length; string hpathPadded = hpath + new string(' ', Math.Max(0, 255 - (int)size)); IRefProp64.SETPATHdll(hpathPadded, ref size); long numComps = 1; string hfld = hfldCore; size = hfld.Length; string hfldPadded = hfld + new string(' ', Math.Max(0, 10000 - (int)size)); string hfmix = "hmx.bnc" + new string(' ', 255); string hrf = "DEF"; string herr = new string(' ', 255); long ierr = 0; long hfldLen = hfldPadded.Length; long hfmixLen = hfmix.Length; long hrfLen = hrf.Length; long herrLen = herr.Length; IRefProp64.SETUPdll(ref numComps, ref hfldPadded, ref hfmix, ref hrf, ref ierr, ref herr, ref hfldLen, ref hfmixLen, ref hrfLen, ref herrLen); if (ierr != 0) { error = $"REFPROP 初始化失败: {herr.Trim()} (ierr={ierr})"; _rpInitialized = false; return false; } _rpInitialized = true; return true; } } catch (Exception ex) { error = $"REFPROP 初始化异常: {ex.Message}"; _rpInitialized = false; return false; } } public bool TrySATP_SaturationByP_MPa(double pressureMPa, out double tSatK, out double Dl_molL, out double Dv_molL, out string error) { tSatK = double.NaN; Dl_molL = double.NaN; Dv_molL = double.NaN; error = string.Empty; double pKPa = pressureMPa * 1000.0; double[] x = new double[20]; x[0] = 1.0; long kph = 1; double Dl = 0; double Dv = 0; double[] xliq = new double[20]; double[] xvap = new double[20]; long ierr = 0; long herrLen = 255; string herr = new string(' ', 255); lock (_refpropLock) { IRefProp64.SATPdll(ref pKPa, x, ref kph, ref tSatK, ref Dl, ref Dv, xliq, xvap, ref ierr, ref herr, ref herrLen); } if (ierr != 0) { error = $"SATP 错误: {herr.Trim()} (ierr={ierr})"; return false; } Dl_molL = Dl; Dv_molL = Dv; return true; } public bool TryTPRHO_VaporDensity_ByTP_MPa_C(double pressureMPa, double temperatureC, out double densityMolPerL, out string error) => throw new NotSupportedException(); public bool TryTHERM_VaporEntropy_ByTD(double temperatureC, double densityMolPerL, out double entropy_kJ_per_kgK, out string error) => throw new NotSupportedException(); public bool TryTPRHO_LiquidDensity_ByTP_MPa_C(double pressureMPa, double temperatureC, out double densityMolPerL, out string error) => throw new NotSupportedException(); public bool TryTHERM_LiquidEnthalpy_ByTD(double temperatureC, double densityMolPerL, out double h_liq_kJ_per_kg, out string error) => throw new NotSupportedException(); public bool TryTHERM_Enthalpy_kJkg_ByT_K_D(double temperatureK, double densityMolPerL, out double h_kJ_per_kg, out string error) => throw new NotSupportedException(); public bool TryConvertMolarDensityToSpecificVolume(double d_molL, out double v_m3kg, out string error) => throw new NotSupportedException(); public bool TryGetIsentropicOutletEnthalpy_h2s_ByP2AndS1_BarA(double dischargePressureBarA, double suctionEntropy_kJkgK, out double h2s_kJkg, out string error) => throw new NotSupportedException(); } } }