diff --git a/CapMachine.Wpf/Services/PPCService.cs b/CapMachine.Wpf/Services/PPCService.cs index 6319dec..bb251c1 100644 --- a/CapMachine.Wpf/Services/PPCService.cs +++ b/CapMachine.Wpf/Services/PPCService.cs @@ -268,58 +268,14 @@ namespace CapMachine.Wpf.Services await Task.Delay(1000); try { - - long iErr, kph = 1; - - double te = 0.0, te1 = 0.0, p = 0.0, p1 = 0.0, d = 0.0, Dl = 0.0, Dv = 0.0, q = 0.0, h = 0.0, ee = 0.0, hh = 0.0, ss = 0.0, cp = 0.0, cv = 0.0, w = 0.0; - double[] x = new double[20], xliq = new double[20], xvap = new double[20]; - double[] xlkg = new double[20], xvkg = new double[20]; - - double tk = 0.0, wm = 0.0, prevDeltaH = 0.0;//prevDeltaH 未使用 - - //textBox5.Text = ""; - - // 幂等初始化:仅首次或工质/路径变化时执行 SETPATH/SETUP,提高每秒循环效率 - if (!EnsureRefpropInitialized(out var initErr)) + if (!TryUpdateSuperheatAndSubcool(out var superheatSubcoolErr)) { - // 初始化失败,跳过本周期 - Logger?.Error($"REFPROP 初始化失败: {initErr}"); - continue; + if (!string.IsNullOrWhiteSpace(superheatSubcoolErr)) + { + Logger?.Error($"过热度/过冷度计算失败: {superheatSubcoolErr}"); + } } - // WMOL 仅在需要时调用;若调用,需设置 x[0]=1.0(纯工质) - x[0] = 1.0; - IRefProp64.WMOLdll(x, ref wm); - - //p = Convert.ToDouble(textBox2.Text) * 1000.0;//textBox2 Comp.吸气压力(kpa) - p = (InhPressTag.EngPvValue) * 100.0;// 保持你原有流程:将 BarA 当作 MPa? 历史代码为 *1000.0,不改变你的算法 - kph = 1; - - p1 = (TxvFrPressTag.EngPvValue) * 100.0;// 保持你原有流程 - //p1 = Convert.ToDouble(textBox3.Text) * 1000.0;//textBox3 Evap.膨胀阀前压力(Mpa) - // 统一放入同一把锁中,避免并发导致的 Fortran 读文件/状态竞态 - string herr = new string(' ', 255); long herrLen = 255; iErr = 0; - lock (_refpropLock) - { - IRefProp64.SATPdll(ref p, x, ref kph, ref te, ref Dl, ref Dv, xliq, xvap, ref iErr, ref herr, ref herrLen); - } - - if (iErr == 0) - Superheat.EngPvValue =Math.Abs(InhTempTag.EngPvValue - (te - 273.15)) ; - else - Superheat.EngPvValue = 0; - - herr = new string(' ', 255); herrLen = 255; iErr = 0; - lock (_refpropLock) - { - IRefProp64.SATPdll(ref p1, x, ref kph, ref te1, ref Dl, ref Dv, xliq, xvap, ref iErr, ref herr, ref herrLen); - } - if (iErr == 0) - Subcool.EngPvValue = Math.Abs(TxvFrTempTag.EngPvValue - (te1 - 273.15)); - else - Subcool.EngPvValue = 0; - - if (TryUpdateThermodynamicSixResults(out var thermoErr)) { if (!string.IsNullOrWhiteSpace(thermoErr)) @@ -345,443 +301,73 @@ namespace CapMachine.Wpf.Services } /// - /// 获取指定组分的摩尔质量(kg/mol) + /// 更新过热度与过冷度结果。 /// - /// 组分ID(icomp) - /// 摩尔质量(kg/mol) - public static double GetMolarMass() - { - // 初始化所有变量,因为我们将使用ref传递 - double wmm = 0, Trp = 0, Tnbpt = 0, Tc = 0, Pc = 0, Dc = 0, Zc = 0, acf = 0, dip = 0, Rgas = 0; - long componentId = 1; - try - { - // 调用INFOdll函数获取物性参数,使用ref - IRefProp64.INFOdll(ref componentId, ref wmm, ref Trp, ref Tnbpt, ref Tc, ref Pc, ref Dc, ref Zc, ref acf, ref dip, ref Rgas); - - // 将wmm从g/mol转换为kg/mol(乘以0.001) - double molarMassKgPerMol = wmm * 0.001; - - return molarMassKgPerMol; - } - catch (Exception ex) - { - throw new Exception($"获取组分{componentId}的摩尔质量时出错: {ex.Message}", ex); - } - } - - - // 若类中尚未定义,请添加全局互斥锁,串行化所有 REFPROP 调用 - private static readonly object _refpropLock = new object(); - - // REFPROP 初始化状态(全局、幂等) - private static volatile bool _rpInitialized = false; - private static string _rpFluidFile = string.Empty; - - /// - /// 幂等初始化:设置流体路径/工质/参考态;确保全局只初始化一次。 - /// 注意:所有 REFPROP 原生调用都需在 _refpropLock 下串行化,包括初始化调用。 - /// - private bool EnsureRefpropInitialized(out string error) + /// + /// 错误汇总输出。 + /// 当两个结果都失败时,返回拼接后的失败原因; + /// 当仅一个结果失败时,只返回该项失败原因; + /// 当至少有一项成功更新时,方法返回 。 + /// + /// + /// 是否至少成功更新了一个结果。 + /// 该方法保持原有行为:过热度和过冷度彼此独立,只要其中之一成功就返回 。 + /// + private bool TryUpdateSuperheatAndSubcool(out string error) { error = string.Empty; - if (_rpInitialized) return true; + bool updated = false; + StringBuilder errorBuilder = new StringBuilder(); - try + // 第一段:收集过热度所需标签,并把实时值直接交给独立计算类。 + // PPCService 只负责“取值 + 回写”,具体热力学过程由 PPCSuperheatSubcoolCalculator 承担。 + if (InhPressTag == null || InhTempTag == null || Superheat == null) { - 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"; - - // 现阶段仅使用 R134A.FLD;如需扩展,可根据 configuredCryogen 选择不同文件 - 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, hfmixLen = hfmix.Length, hrfLen = hrf.Length, 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; - _rpFluidFile = hfldCore; - return true; - } + AppendCalculationError(errorBuilder, "缺少过热度计算标签"); } - catch (Exception ex) + else if (_superheatSubcoolCalculator.TryCalculateSuperheatK(InhPressTag.EngPvValue, InhTempTag.EngPvValue, out var superheatValue, out var superheatErr)) { - error = $"REFPROP 初始化异常: {ex.Message}"; - Logger.Error(error); - _rpInitialized = false; - return false; + Superheat.EngPvValue = superheatValue; + updated = true; } + else + { + AppendCalculationError(errorBuilder, superheatErr); + } + + // 第二段:收集过冷度所需标签,并按同样方式委托给独立计算类。 + // 两个结果互不阻塞,保持与旧实现一致的“部分成功也可回写”的策略。 + if (TxvFrPressTag == null || TxvFrTempTag == null || Subcool == null) + { + AppendCalculationError(errorBuilder, "缺少过冷度计算标签"); + } + else if (_superheatSubcoolCalculator.TryCalculateSubcoolK(TxvFrPressTag.EngPvValue, TxvFrTempTag.EngPvValue, out var subcoolValue, out var subcoolErr)) + { + Subcool.EngPvValue = subcoolValue; + updated = true; + } + else + { + AppendCalculationError(errorBuilder, subcoolErr); + } + + error = errorBuilder.ToString(); + return updated; } - /// - /// - /// 计算获取 气相密度D_vap mol/l - /// - /// TPRHOdll 封装:按 T(℃)、P(MPa) 计算“气相”摩尔密度 D [mol/L] - /// LabVIEW 对应:kph=2(气相),kguess=0 - /// - /// 气体阀前压力mpa - /// 气体阀前温度 C - /// “气相”摩尔密度 D [mol/L] / 气相密度D_vap mol/l - /// ERR - /// - public bool TryTPRHO_VaporDensity_ByTP_MPa_C( - double pressureMPa, - double temperatureC, - out double densityMolPerL, - out string error) + private static void AppendCalculationError(StringBuilder errorBuilder, string error) { - densityMolPerL = double.NaN; - error = string.Empty; - - // 输入换算 - double tK = temperatureC + 273.15; // K - double pKPa = pressureMPa * 1000.0; // MPa -> kPa - - // 纯工质:x[0]=1 - double[] x = new double[20]; - x[0] = 1.0; - - // TPRHO 参数 - long kph = 2; // 气相 - long kguess = 0; // 让例程自行选择初值 - double D = 0.0; // 输出:mol/L - long ierr = 0; - long herrLen = 255; - string herr = new string(' ', 255); - - lock (_refpropLock) + if (string.IsNullOrWhiteSpace(error)) { - IRefProp64.TPRHOdll(ref tK, ref pKPa, x, ref kph, ref kguess, ref D, ref ierr, ref herr, ref herrLen); + return; } - if (ierr != 0) + if (errorBuilder.Length > 0) { - error = $"TPRHO 错误: {herr.Trim()} (ierr={ierr})"; - return false; + errorBuilder.Append(";"); } - densityMolPerL = D; // mol/L - return true; - } - - - /// - /// 计算气相熵(单相气相)。 - /// - /// THERMdll 封装:按 T(℃) 与上一步得到的 D[mol/L] 计算气相熵 s [kJ/(kg·K)]。 - /// LabVIEW 对应:THERM(T, D) -> s[J/(mol·K)],再 /M[kg/mol] 并 *0.001。 - /// - /// 气体阀前温度 C - /// 气相密度D_vap mol/l - /// - /// - /// - public bool TryTHERM_VaporEntropy_ByTD( - double temperatureC, - double densityMolPerL, - out double entropy_kJ_per_kgK, - out string error) - { - entropy_kJ_per_kgK = double.NaN; - error = string.Empty; - - // 输入换算 - double tK = temperatureC + 273.15; // K - double D = densityMolPerL; // mol/L - - // 纯工质:x[0]=1 - double[] x = new double[20]; - x[0] = 1.0; - - // THERM 输出 - double pOut = 0, e = 0, hJmol = 0, sJmolK = 0, cv = 0, cp = 0, w = 0, hjt = 0; - - // 摩尔质量(kg/mol)——使用你已封装的 GetMolarMass() - double molarMassKgPerMol = GetMolarMass(); - if (molarMassKgPerMol <= 0) - { - error = "无效的摩尔质量"; - return false; - } - - lock (_refpropLock) - { - IRefProp64.THERMdll(ref tK, ref D, x, ref pOut, ref e, ref hJmol, ref sJmolK, ref cv, ref cp, ref w, ref hjt); - // THERM 不返回 ierr,如输入越界会在上游 TPRHO 就失败 - } - - // J/(mol·K) -> kJ/(kg·K):先除以 kg/mol,再乘 0.001 - entropy_kJ_per_kgK = (sJmolK / molarMassKgPerMol) * 0.001; - return true; - } - - - /// - /// - /// 计算液路阀前液相密度D_liq(单相液相) 液相密度D_liq mol/L - /// - /// TPRHOdll 封装(液相):按 T(℃)、P(MPa) 计算“液相”摩尔密度 D_liq [mol/L] - /// LabVIEW 对应:kph=1(液相),kguess=0 - /// - /// - /// - /// - /// - /// - public bool TryTPRHO_LiquidDensity_ByTP_MPa_C( - double pressureMPa, - double temperatureC, - out double densityMolPerL, - out string error) - { - densityMolPerL = double.NaN; - error = string.Empty; - - // 输入换算 - double tK = temperatureC + 273.15; // K - double pKPa = pressureMPa * 1000.0; // MPa -> kPa - - // 纯工质:x[0]=1 - double[] x = new double[20]; - x[0] = 1.0; - - // TPRHO 参数 - long kph = 1; // 液相 - long kguess = 0; // 初值由例程选择 - double D = 0.0; // 输出:mol/L - long ierr = 0; - long herrLen = 255; - string herr = new string(' ', 255); - - lock (_refpropLock) - { - IRefProp64.TPRHOdll(ref tK, ref pKPa, x, ref kph, ref kguess, ref D, ref ierr, ref herr, ref herrLen); - } - - if (ierr != 0) - { - error = $"TPRHO(液相) 错误: {herr.Trim()} (ierr={ierr})"; - return false; - } - - densityMolPerL = D; // mol/L - return true; - } - - - /// - /// 计算获取 液相质量焓h lig k/kg - /// THERMdll 封装(液相):按 T(℃) 与 D_liq[mol/L] 计算液相质量焓 h_liq [kJ/kg] - /// LabVIEW 对应:THERM(T, D) -> h[J/mol],再 /M[kg/mol] * 0.001 - /// - /// 液体阀前温度'℃ - /// 液相密度D_liq mol/L - /// 液相质量焓h lig k/kg - /// Err - /// - public bool TryTHERM_LiquidEnthalpy_ByTD( - double temperatureC, - double densityMolPerL, - out double h_liq_kJ_per_kg, - out string error) - { - h_liq_kJ_per_kg = double.NaN; - error = string.Empty; - - // 输入换算 - double tK = temperatureC + 273.15; // K - double D = densityMolPerL; // mol/L - - // 纯工质:x[0]=1 - double[] x = new double[20]; - x[0] = 1.0; - - // THERM 输出 - double pOut = 0, e = 0, hJmol = 0, sJmolK = 0, cv = 0, cp = 0, w = 0, hjt = 0; - - // 摩尔质量(kg/mol)——使用已封装的 GetMolarMass() - double molarMassKgPerMol = GetMolarMass(); - if (molarMassKgPerMol <= 0) - { - error = "无效的摩尔质量"; - return false; - } - - lock (_refpropLock) - { - IRefProp64.THERMdll(ref tK, ref D, x, ref pOut, ref e, ref hJmol, ref sJmolK, ref cv, ref cp, ref w, ref hjt); - // THERM 不返回 ierr,越界一般在上游 TPRHO 即失败 - } - - // J/mol -> kJ/kg:先除以 kg/mol,再乘 0.001 - h_liq_kJ_per_kg = (hJmol / molarMassKgPerMol) * 0.001; - return true; - } - - - /// - /// SATPdll 封装:由压力 P(MPa) 求饱和温度 Tsat[K]、饱和液/气摩尔密度 Dl/Dv [mol/L] - /// LabVIEW 对应:P(MPa)*1000 -> kPa;kph=1(bubble) - /// - /// 王缩机吸气压力mpa - /// 吸气压力下的饱和温度T_sat K - /// 饱和液相密度Dl molL - /// 饱和气相密度Dv mol/L - /// - /// - 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; // MPa -> kPa - double[] x = new double[20]; x[0] = 1.0; - - long kph = 1; // 饱和液侧(bubble),纯工质下 dew/bubble 的 Tsat 一致 - double Dl = 0, Dv = 0; - double[] xliq = new double[20]; - double[] xvap = new double[20]; - long ierr = 0, 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; - } - - - /// - /// THERMdll 封装:由 T[K] 与 D[mol/L] 计算质量比焓 h[kJ/kg] - /// LabVIEW 对应:THERM(T, D) -> h[J/mol],再 /M[kg/mol] * 0.001 - /// - /// - /// - /// - /// - /// - public bool TryTHERM_Enthalpy_kJkg_ByT_K_D( - double temperatureK, - double densityMolPerL, - out double h_kJ_per_kg, - out string error) - { - h_kJ_per_kg = double.NaN; - error = string.Empty; - - double tK = temperatureK; - double D = densityMolPerL; - - double[] x = new double[20]; x[0] = 1.0; - - double pOut = 0, e = 0, hJmol = 0, sJmolK = 0, cv = 0, cp = 0, w = 0, hjt = 0; - - double molarMassKgPerMol = GetMolarMass(); - if (molarMassKgPerMol <= 0) - { - error = "无效的摩尔质量"; - return false; - } - - lock (_refpropLock) - { - IRefProp64.THERMdll(ref tK, ref D, x, ref pOut, ref e, ref hJmol, ref sJmolK, ref cv, ref cp, ref w, ref hjt); - } - - h_kJ_per_kg = (hJmol / molarMassKgPerMol) * 0.001; // J/mol -> kJ/kg - return true; - } - - - /// - /// 便捷:由压力 P(MPa) 直接得到“饱和液”质量比焓 h_liq[kJ/kg] - /// 流程:SATP(P)->Tsat/Dl,再 THERM(Tsat, Dl) - /// - /// - /// - /// - /// - public bool TryGetSaturationLiquidEnthalpy_ByP_MPa( - double pressureMPa, - out double h_liq_kJkg, - out string error) - { - h_liq_kJkg = double.NaN; - error = string.Empty; - - if (!TrySATP_SaturationByP_MPa(pressureMPa, out double tSatK, out double Dl, out _, out error)) - return false; - - return TryTHERM_Enthalpy_kJkg_ByT_K_D(tSatK, Dl, out h_liq_kJkg, out error); - } - - /// - /// 便捷:由压力 P(MPa) 直接得到“饱和气”质量比焓 h_vap[kJ/kg] - /// 流程:SATP(P)->Tsat/Dv,再 THERM(Tsat, Dv) - /// - /// - /// - /// - /// - public bool TryGetSaturationVaporEnthalpy_ByP_MPa( - double pressureMPa, - out double h_vap_kJkg, - out string error) - { - h_vap_kJkg = double.NaN; - error = string.Empty; - - if (!TrySATP_SaturationByP_MPa(pressureMPa, out double tSatK, out _, out double Dv, out error)) - return false; - - return TryTHERM_Enthalpy_kJkg_ByT_K_D(tSatK, Dv, out h_vap_kJkg, out error); + errorBuilder.Append(error); } /// @@ -1031,476 +617,121 @@ namespace CapMachine.Wpf.Services { error = string.Empty; - if (!TryGetCompressorPower_kW(out var w_kW, out var wErr)) + // 先把本周期所需的标签值组装为输入模型。 + // PPCService 在这里负责完成“标签 -> 领域输入对象”的映射, + // 从而把计算类和 UI/标签层解耦。 + if (!TryCreateThermodynamicSixResultsInput(out var input, out var inputErr)) { - error = wErr; - return false; - } - if (!TryGetRefrigerantMassFlow_kg_s(out var mRef_kg_s, out var mRefErr)) - { - error = mRefErr; + error = inputErr; return false; } - if (!TryGetVaporPointState_ByTP_BarA_C(InhPressTag.EngPvValue, InhTempTag.EngPvValue, out var h1_kJkg, out var s1_kJkgK, out var v1_m3kg, out var p1Err)) + // 把输入模型交给独立计算类统一完成 6 个结果的热力计算。 + // 此处不再在 PPCService 中展开公式,只保留流程调度职责。 + if (!_thermodynamicSixResultsCalculator.TryCalculate(input, out var result, out error)) { - error = $"h1/s1/吸气比容计算失败: {p1Err}"; return false; } - if (ExPressTag == null || ExTempTag == null) - { - error = "缺少排气压力/排气温度标签"; - return false; - } - if (!TryGetVaporPointEnthalpy_ByTP_BarA_C(ExPressTag.EngPvValue, ExTempTag.EngPvValue, out var h2_kJkg, out var p2Err)) - { - error = $"h2 计算失败: {p2Err}"; - return false; - } + // 先把结果同步到服务属性,供界面绑定或其他业务读取。 + HeatingCapacityQh_kW = result.HeatingCapacityQh_kW; + CoolingCapacityQc_kW = result.CoolingCapacityQc_kW; + COPHeating = result.COPHeating; + COPCooling = result.COPCooling; + IsentropicEfficiencyPct = result.IsentropicEfficiencyPct; - if (!TryGetLiquidPointEnthalpy_ByTP_BarA_C(TxvFrPressTag.EngPvValue, TxvFrTempTag.EngPvValue, out var h3_kJkg, out var p3Err)) - { - error = $"h3 计算失败: {p3Err}"; - return false; - } - - if (!TryGetIsentropicOutletEnthalpy_h2s_ByP2AndS1_BarA(ExPressTag.EngPvValue, s1_kJkgK, out var h2s_kJkg, out var h2sErr)) - { - error = $"h2s 计算失败: {h2sErr}"; - return false; - } - - if (!TryComputeCapacitiesAndCOP(mRef_kg_s, h1_kJkg, h2_kJkg, h3_kJkg, w_kW, out var qh_kW, out var qc_kW, out var copH, out var copC, out var capErr)) - { - error = capErr; - return false; - } - - if (!TryComputeIsentropicEfficiencyPct(h1_kJkg, h2_kJkg, h2s_kJkg, out var etaS_pct, out var etaSErr)) - { - error = etaSErr; - return false; - } - - HeatingCapacityQh_kW = qh_kW; - CoolingCapacityQc_kW = qc_kW; - COPHeating = copH; - COPCooling = copC; - IsentropicEfficiencyPct = etaS_pct; - - if (!TryComputeVolumetricEfficiencyPct(mRef_kg_s, v1_m3kg, out var etaV_pct, out var etaVErr)) - { - VolumetricEfficiencyPct = double.NaN; - error = etaVErr; - return true; - } - - VolumetricEfficiencyPct = etaV_pct; - - //赋值 + // 再把结果回写到对应工程量标签。 HeatingCapacity.EngPvValue = HeatingCapacityQh_kW; COPHeat.EngPvValue = COPHeating; IsentrpEff.EngPvValue = IsentropicEfficiencyPct; CoolCapacity.EngPvValue = CoolingCapacityQc_kW; COPCool.EngPvValue = COPCooling; - VoltricEff.EngPvValue = VolumetricEfficiencyPct; + + // 容积效率在旧逻辑中允许单独失败; + // 因此只有拿到有效数值时才回写,避免覆盖已有结果为 NaN/Inf。 + if (!double.IsNaN(result.VolumetricEfficiencyPct) && !double.IsInfinity(result.VolumetricEfficiencyPct)) + { + VolumetricEfficiencyPct = result.VolumetricEfficiencyPct; + VoltricEff.EngPvValue = VolumetricEfficiencyPct; + } return true; } /// - /// 获取压缩机输入功率 W。 + /// 创建六个热力结果值计算所需的输入模型。 /// - /// 压缩机功率输出,单位 kW(由 HV[W] / 1000 换算)。 - /// 失败原因(如缺少标签或功率为 0/NaN)。 - /// 是否获取成功。 - private bool TryGetCompressorPower_kW(out double w_kW, out string error) + /// + /// 输出输入模型。 + /// 该对象是 之间的数据边界: + /// 由服务层负责从标签提取实时值,由计算层只消费该对象而不直接依赖标签。 + /// + /// 失败原因,通常为缺少标签、缺少排量配置或关键输入不可用。 + /// 是否创建成功。 + private bool TryCreateThermodynamicSixResultsInput(out PPCThermodynamicSixResultsCalculationInput input, out string error) { - w_kW = double.NaN; + input = new PPCThermodynamicSixResultsCalculationInput(); error = string.Empty; + // 依次校验本周期计算所需的全部标签。 + // 这里不做公式计算,只保证独立计算类拿到的是一套完整输入。 if (HVPwTag == null) { error = "缺少 HV[W] 功率标签"; return false; } - - double w_W = HVPwTag.EngPvValue; - if (double.IsNaN(w_W) || double.IsInfinity(w_W) || w_W <= 0) + if (VRVTag == null) { - error = $"无效压缩机功率 HV[W]={w_W}"; + error = "缺少总流量(冷媒流量)标签"; return false; } - w_kW = w_W / 1000.0; - return true; - } - - /// - /// 获取冷媒质量流量(按流程图:总流量 - 油流量)。 - /// - /// - /// 冷媒质量流量输出,单位 kg/s。 - /// 计算:mRef = (VRV[kg/h] - Oil[kg/h]) / 3600。 - /// - /// 失败原因(如流量为 NaN/Inf 或计算后小于等于 0)。 - /// 是否获取成功。 - private bool TryGetRefrigerantMassFlow_kg_s(out double mRef_kg_s, out string error) - { - mRef_kg_s = double.NaN; - error = string.Empty; - - double mTotal_kg_h = VRVTag.EngPvValue; - double mOil_kg_h = LubeFlowTag.EngPvValue; - - if (double.IsNaN(mTotal_kg_h) || double.IsInfinity(mTotal_kg_h)) + if (LubeFlowTag == null) { - error = "总流量(冷媒流量)为 NaN/Inf"; + error = "缺少油流量标签"; return false; } - if (double.IsNaN(mOil_kg_h) || double.IsInfinity(mOil_kg_h)) + if (InhPressTag == null || InhTempTag == null) { - error = "油流量为 NaN/Inf"; + error = "缺少吸气压力/吸气温度标签"; return false; } - - double mRef_kg_h = mTotal_kg_h - mOil_kg_h; - if (mRef_kg_h <= 0) + if (ExPressTag == null || ExTempTag == null) { - error = $"冷媒质量流量<=0,总流量={mTotal_kg_h}kg/h,油流量={mOil_kg_h}kg/h"; + error = "缺少排气压力/排气温度标签"; return false; } - - mRef_kg_s = mRef_kg_h / 3600.0; - return true; - } - - /// - /// 按流程图(TPRHO + THERM)计算气相点的质量比焓 h。 - /// - /// 压力,单位 BarA(绝压)。内部换算为 MPa:P_MPa = BarA * 0.1。 - /// 温度,单位 ℃。 - /// 质量比焓输出,单位 kJ/kg。 - /// 失败原因。 - /// 是否计算成功。 - private bool TryGetVaporPointEnthalpy_ByTP_BarA_C(double pressureBarA, double temperatureC, out double h_kJkg, out string error) - { - h_kJkg = double.NaN; - error = string.Empty; - - double pMPa = pressureBarA * 0.1; - if (!TryTPRHO_VaporDensity_ByTP_MPa_C(pMPa, temperatureC, out var d_molL, out var dErr)) + if (TxvFrPressTag == null || TxvFrTempTag == null) { - error = dErr; + error = "缺少膨胀阀前压力/膨胀阀前温度标签"; return false; } - - double tK = temperatureC + 273.15; - if (!TryTHERM_Enthalpy_kJkg_ByT_K_D(tK, d_molL, out h_kJkg, out var hErr)) - { - error = hErr; - return false; - } - - return true; - } - - /// - /// 按流程图(TPRHO + THERM)计算液相点的质量比焓 h。 - /// - /// 压力,单位 BarA(绝压)。内部换算为 MPa:P_MPa = BarA * 0.1。 - /// 温度,单位 ℃。 - /// 质量比焓输出,单位 kJ/kg。 - /// 失败原因。 - /// 是否计算成功。 - private bool TryGetLiquidPointEnthalpy_ByTP_BarA_C(double pressureBarA, double temperatureC, out double h_kJkg, out string error) - { - h_kJkg = double.NaN; - error = string.Empty; - - double pMPa = pressureBarA * 0.1; - if (!TryTPRHO_LiquidDensity_ByTP_MPa_C(pMPa, temperatureC, out var d_molL, out var dErr)) - { - error = dErr; - return false; - } - - if (!TryTHERM_LiquidEnthalpy_ByTD(temperatureC, d_molL, out h_kJkg, out var hErr)) - { - error = hErr; - return false; - } - - return true; - } - - /// - /// 按流程图(TPRHO + THERM)计算气相点状态量:质量比焓 h、质量比熵 s、比容 v。 - /// - /// 压力,单位 BarA(绝压)。内部换算为 MPa。 - /// 温度,单位 ℃。 - /// 质量比焓输出,单位 kJ/kg。 - /// 质量比熵输出,单位 kJ/(kg·K)。 - /// 比容输出,单位 m³/kg。 - /// 失败原因。 - /// 是否计算成功。 - private bool TryGetVaporPointState_ByTP_BarA_C( - double pressureBarA, - double temperatureC, - out double h_kJkg, - out double s_kJkgK, - out double v_m3kg, - out string error) - { - h_kJkg = double.NaN; - s_kJkgK = double.NaN; - v_m3kg = double.NaN; - error = string.Empty; - - double pMPa = pressureBarA * 0.1; - if (!TryTPRHO_VaporDensity_ByTP_MPa_C(pMPa, temperatureC, out var d_molL, out var dErr)) - { - error = dErr; - return false; - } - - double tK = temperatureC + 273.15; - if (!TryTHERM_Enthalpy_kJkg_ByT_K_D(tK, d_molL, out h_kJkg, out var hErr)) - { - error = hErr; - return false; - } - if (!TryTHERM_VaporEntropy_ByTD(temperatureC, d_molL, out s_kJkgK, out var sErr)) - { - error = sErr; - return false; - } - if (!TryConvertMolarDensityToSpecificVolume(d_molL, out v_m3kg, out var vErr)) - { - error = vErr; - return false; - } - - return true; - } - - /// - /// P-S 闪蒸(等熵压缩理想出口):由排气压力 P2 与吸气熵 s1 计算等熵出口焓 h2s。 - /// - /// 排气压力 P2,单位 BarA(绝压)。内部换算为 kPa。 - /// 吸气熵 s1,单位 kJ/(kg·K)。内部换算为 J/(mol·K) 作为 PSFLSH 输入。 - /// 等熵出口焓 h2s 输出,单位 kJ/kg。 - /// 失败原因(初始化 REFPROP 失败、PSFLSH 失败、换算失败等)。 - /// 是否计算成功。 - private bool TryGetIsentropicOutletEnthalpy_h2s_ByP2AndS1_BarA(double dischargePressureBarA, double suctionEntropy_kJkgK, out double h2s_kJkg, out string error) - { - h2s_kJkg = double.NaN; - error = string.Empty; - - if (!EnsureRefpropInitialized(out var initErr)) - { - error = initErr; - return false; - } - - double pKPa = dischargePressureBarA * 100.0; - if (pKPa <= 0) - { - error = $"无效排气压力: {dischargePressureBarA} BarA"; - return false; - } - - if (double.IsNaN(suctionEntropy_kJkgK) || double.IsInfinity(suctionEntropy_kJkgK)) - { - error = "无效吸气熵 s1"; - return false; - } - - if (!TryConvertS_kJkgK_To_JmolK(suctionEntropy_kJkgK, out var s_JmolK, out var convErr)) - { - error = convErr; - return false; - } - - double[] z = new double[20]; - z[0] = 1.0; - - double t = 0.0; - double d = 0.0; - double Dl = 0.0; - double Dv = 0.0; - double[] xliq = new double[20]; - double[] xvap = new double[20]; - double q = 0.0; - double ee = 0.0; - double h = 0.0; - double Cv = 0.0; - double Cp = 0.0; - double w = 0.0; - long ierr = 0; - long herrLen = 255; - string herr = new string(' ', 255); - - lock (_refpropLock) - { - IRefProp64.PSFLSHdll(ref pKPa, ref s_JmolK, z, ref t, ref d, ref Dl, ref Dv, xliq, xvap, ref q, ref ee, ref h, ref Cv, ref Cp, ref w, ref ierr, ref herr, ref herrLen); - } - - if (ierr != 0) - { - error = $"PSFLSH 错误: {herr.Trim()} (ierr={ierr})"; - return false; - } - - if (!TryConvertH_Jmol_To_kJkg(h, out h2s_kJkg, out var hConvErr)) - { - error = hConvErr; - return false; - } - - return true; - } - - /// - /// 按流程图计算:制热量/制冷量与 COP。 - /// - /// 冷媒质量流量,单位 kg/s。 - /// 点1(吸气)质量比焓,单位 kJ/kg。 - /// 点2(排气)质量比焓,单位 kJ/kg。 - /// 点3(阀前)质量比焓,单位 kJ/kg。 - /// 压缩机功率,单位 kW。 - /// 制热量输出,单位 kW,计算:mRef*(h2-h3)。 - /// 制冷量输出,单位 kW,计算:mRef*(h1-h3)。 - /// COP(制热)输出,计算:Qh/W。 - /// COP(制冷)输出,计算:Qc/W。 - /// 失败原因。 - /// 是否计算成功。 - private bool TryComputeCapacitiesAndCOP( - double mRef_kg_s, - double h1_kJkg, - double h2_kJkg, - double h3_kJkg, - double w_kW, - out double qh_kW, - out double qc_kW, - out double copHeating, - out double copCooling, - out string error) - { - qh_kW = double.NaN; - qc_kW = double.NaN; - copHeating = double.NaN; - copCooling = double.NaN; - error = string.Empty; - - if (mRef_kg_s <= 0 || double.IsNaN(mRef_kg_s) || double.IsInfinity(mRef_kg_s)) - { - error = "无效冷媒质量流量"; - return false; - } - if (w_kW <= 0 || double.IsNaN(w_kW) || double.IsInfinity(w_kW)) - { - error = "无效压缩机功率"; - return false; - } - - qh_kW = mRef_kg_s * (h2_kJkg - h3_kJkg); - qc_kW = mRef_kg_s * (h1_kJkg - h3_kJkg); - - copHeating = qh_kW / w_kW; - copCooling = qc_kW / w_kW; - return true; - } - - /// - /// 按流程图计算等熵效率: - /// ηs[%] = (h2s - h1) / (h2 - h1) * 100。 - /// - /// 点1(吸气)质量比焓,单位 kJ/kg。 - /// 点2(排气)质量比焓,单位 kJ/kg。 - /// 等熵出口质量比焓,单位 kJ/kg。 - /// 等熵效率输出,单位 %。 - /// 失败原因(如分母 h2-h1 过小等)。 - /// 是否计算成功。 - private bool TryComputeIsentropicEfficiencyPct(double h1_kJkg, double h2_kJkg, double h2s_kJkg, out double etaS_pct, out string error) - { - etaS_pct = double.NaN; - error = string.Empty; - - double denom = h2_kJkg - h1_kJkg; - const double eps = 1e-9; - if (Math.Abs(denom) < eps) - { - error = "等熵效率分母(h2-h1)过小"; - return false; - } - - double eta = (h2s_kJkg - h1_kJkg) / denom; - if (double.IsNaN(eta) || double.IsInfinity(eta)) - { - error = "等熵效率结果异常"; - return false; - } - - etaS_pct = eta * 100.0; - return true; - } - - /// - /// 按流程图计算容积效率: - /// ηv[%] = V_suc / V_theo * 100。 - /// - /// 冷媒质量流量,单位 kg/s。 - /// 吸气比容,单位 m³/kg。 - /// 容积效率输出,单位 %。 - /// 失败原因(缺少转速/排量、输入异常、理论体积流量为 0 等)。 - /// 是否计算成功。 - private bool TryComputeVolumetricEfficiencyPct(double mRef_kg_s, double v1_m3kg, out double etaV_pct, out string error) - { - etaV_pct = double.NaN; - error = string.Empty; - if (SpeedTag == null) { error = "缺少转速标签"; return false; } - - double speed_rpm = SpeedTag.EngPvValue; - if (double.IsNaN(speed_rpm) || double.IsInfinity(speed_rpm) || speed_rpm <= 0) + if (!TryGetCompressorDisplacement_cc(out var displacementCc, out var displacementErr)) { - error = $"无效转速: {speed_rpm} rpm"; + error = displacementErr; return false; } - if (!TryGetCompressorDisplacement_cc(out var disp_cc, out var dispErr)) + // 将实时标签值与配置值组装为独立计算类可直接消费的输入对象。 + input = new PPCThermodynamicSixResultsCalculationInput { - error = dispErr; - return false; - } + CompressorPowerW = HVPwTag.EngPvValue, + TotalMassFlowKgPerHour = VRVTag.EngPvValue, + OilMassFlowKgPerHour = LubeFlowTag.EngPvValue, + SuctionPressureBarA = InhPressTag.EngPvValue, + SuctionTemperatureC = InhTempTag.EngPvValue, + DischargePressureBarA = ExPressTag.EngPvValue, + DischargeTemperatureC = ExTempTag.EngPvValue, + LiquidPressureBarA = TxvFrPressTag.EngPvValue, + LiquidTemperatureC = TxvFrTempTag.EngPvValue, + CompressorSpeedRpm = SpeedTag.EngPvValue, + CompressorDisplacementCc = displacementCc + }; - if (double.IsNaN(v1_m3kg) || double.IsInfinity(v1_m3kg) || v1_m3kg <= 0) - { - error = "无效吸气比容"; - return false; - } - - if (double.IsNaN(mRef_kg_s) || double.IsInfinity(mRef_kg_s) || mRef_kg_s <= 0) - { - error = "无效冷媒质量流量"; - return false; - } - - double suctionVolFlow_m3_h = (mRef_kg_s * 3600.0) * v1_m3kg; - - double theoVolFlow_m3_h = (speed_rpm / 60.0) * disp_cc * 0.0036; - if (theoVolFlow_m3_h <= 0) - { - error = "理论吸气体积流量<=0"; - return false; - } - - etaV_pct = (suctionVolFlow_m3_h / theoVolFlow_m3_h) * 100.0; return true; } @@ -1541,190 +772,6 @@ namespace CapMachine.Wpf.Services return true; } - /// - /// REFPROP TPFLSH 封装(T-P 闪蒸): - /// 输入压力/温度,输出摩尔密度、摩尔焓、摩尔熵等,用于后续换算质量基准的 h/s 以及比容 v。 - /// - /// 压力输入,单位 BarA(绝压)。内部换算为 kPa:P_kPa = BarA * 100。 - /// 温度输入,单位 ℃。内部换算为 K:T_K = ℃ + 273.15。 - /// 整体摩尔密度输出,单位 mol/L。 - /// 摩尔焓输出,单位 J/mol。 - /// 摩尔熵输出,单位 J/(mol·K)。 - /// 温度输出,单位 K(REFPROP 可能返回收敛后的温度)。 - /// 失败原因(初始化 REFPROP 失败、TPFLSH 错误等)。 - /// 是否计算成功。 - private bool TryTPFLSH_ByTP_BarA_C( - double pressureBarA, - double temperatureC, - out double d_molL, - out double h_Jmol, - out double s_JmolK, - out double t_K, - out string error) - { - d_molL = double.NaN; - h_Jmol = double.NaN; - s_JmolK = double.NaN; - t_K = double.NaN; - error = string.Empty; - - if (!EnsureRefpropInitialized(out var initErr)) - { - error = initErr; - return false; - } - - double t = temperatureC + 273.15; - double pKPa = pressureBarA * 100.0; - - if (pKPa <= 0) - { - error = $"无效压力: {pressureBarA} BarA"; - return false; - } - - double[] x = new double[20]; - x[0] = 1.0; - - double d = 0.0; - double Dl = 0.0; - double Dv = 0.0; - double[] xliq = new double[20]; - double[] xvap = new double[20]; - double q = 0.0; - double ee = 0.0; - double h = 0.0; - double ss = 0.0; - double Cv = 0.0; - double Cp = 0.0; - double w = 0.0; - long ierr = 0; - long herrLen = 255; - string herr = new string(' ', 255); - - lock (_refpropLock) - { - IRefProp64.TPFLSHdll(ref t, ref pKPa, x, ref d, ref Dl, ref Dv, xliq, xvap, ref q, ref ee, ref h, ref ss, ref Cv, ref Cp, ref w, ref ierr, ref herr, ref herrLen); - } - - if (ierr != 0) - { - error = $"TPFLSH 错误: {herr.Trim()} (ierr={ierr})"; - return false; - } - - d_molL = d; - h_Jmol = h; - s_JmolK = ss; - t_K = t; - return true; - } - - /// - /// 单位换算:J/mol -> kJ/kg。 - /// - /// 摩尔焓,单位 J/mol。 - /// 质量比焓输出,单位 kJ/kg。 - /// 失败原因(摩尔质量无效等)。 - /// 是否换算成功。 - private bool TryConvertH_Jmol_To_kJkg(double h_Jmol, out double h_kJkg, out string error) - { - h_kJkg = double.NaN; - error = string.Empty; - - double molarMassKgPerMol = GetMolarMass(); - if (molarMassKgPerMol <= 0) - { - error = "无效的摩尔质量"; - return false; - } - - h_kJkg = (h_Jmol / molarMassKgPerMol) * 0.001; - return true; - } - - /// - /// 单位换算:J/(mol·K) -> kJ/(kg·K)。 - /// - /// 摩尔熵,单位 J/(mol·K)。 - /// 质量比熵输出,单位 kJ/(kg·K)。 - /// 失败原因(摩尔质量无效等)。 - /// 是否换算成功。 - private bool TryConvertS_JmolK_To_kJkgK(double s_JmolK, out double s_kJkgK, out string error) - { - s_kJkgK = double.NaN; - error = string.Empty; - - double molarMassKgPerMol = GetMolarMass(); - if (molarMassKgPerMol <= 0) - { - error = "无效的摩尔质量"; - return false; - } - - s_kJkgK = (s_JmolK / molarMassKgPerMol) * 0.001; - return true; - } - - /// - /// 单位换算:kJ/(kg·K) -> J/(mol·K)。 - /// - /// 质量比熵,单位 kJ/(kg·K)。 - /// 摩尔熵输出,单位 J/(mol·K)。 - /// 失败原因(摩尔质量无效等)。 - /// 是否换算成功。 - private bool TryConvertS_kJkgK_To_JmolK(double s_kJkgK, out double s_JmolK, out string error) - { - s_JmolK = double.NaN; - error = string.Empty; - - double molarMassKgPerMol = GetMolarMass(); - if (molarMassKgPerMol <= 0) - { - error = "无效的摩尔质量"; - return false; - } - - s_JmolK = s_kJkgK * 1000.0 * molarMassKgPerMol; - return true; - } - - /// - /// 单位换算:摩尔密度 mol/L -> 比容 m³/kg。 - /// - /// 摩尔密度输入,单位 mol/L。 - /// 比容输出,单位 m³/kg。 - /// 失败原因(摩尔密度/摩尔质量无效等)。 - /// 是否换算成功。 - private bool TryConvertMolarDensityToSpecificVolume(double d_molL, out double v_m3kg, out string error) - { - v_m3kg = double.NaN; - error = string.Empty; - - if (double.IsNaN(d_molL) || double.IsInfinity(d_molL) || d_molL <= 0) - { - error = $"无效摩尔密度: {d_molL} mol/L"; - return false; - } - - double molarMassKgPerMol = GetMolarMass(); - if (molarMassKgPerMol <= 0) - { - error = "无效的摩尔质量"; - return false; - } - - double rho_kg_m3 = d_molL * molarMassKgPerMol * 1000.0; - if (rho_kg_m3 <= 0) - { - error = $"无效密度: {rho_kg_m3} kg/m3"; - return false; - } - - v_m3kg = 1.0 / rho_kg_m3; - return true; - } - #endregion } } diff --git a/CapMachine.Wpf/Services/PPCSuperheatSubcoolCalculationInput.cs b/CapMachine.Wpf/Services/PPCSuperheatSubcoolCalculationInput.cs new file mode 100644 index 0000000..c09b666 --- /dev/null +++ b/CapMachine.Wpf/Services/PPCSuperheatSubcoolCalculationInput.cs @@ -0,0 +1,368 @@ +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(); + } + } +} diff --git a/CapMachine.Wpf/Services/PPCThermodynamicSixResultsCalculationInput.cs b/CapMachine.Wpf/Services/PPCThermodynamicSixResultsCalculationInput.cs new file mode 100644 index 0000000..ffe7411 --- /dev/null +++ b/CapMachine.Wpf/Services/PPCThermodynamicSixResultsCalculationInput.cs @@ -0,0 +1,1455 @@ +using CapMachine.Core; +using CapMachine.Wpf.PPCalculation; +using System; + +namespace CapMachine.Wpf.Services +{ + /// + /// 六个热力结果值计算的输入数据。 + /// + /// + /// 该输入对象对应六个结果值计算过程中用到的全部外部测点/配置量。 + /// 所有字段均采用当前系统中已经约定好的工程单位,不在本对象中做任何换算。 + /// + public sealed class PPCThermodynamicSixResultsCalculationInput + { + /// + /// 压缩机输入功率,单位 W。 + /// 当前来源为 HV 功率标签。 + /// + public double CompressorPowerW { get; set; } + + /// + /// 总流量(冷媒流量标签当前语义),单位 kg/h。 + /// 该值会与油流量做差,得到真正参与热力计算的冷媒质量流量。 + /// + public double TotalMassFlowKgPerHour { get; set; } + + /// + /// 润滑油流量,单位 kg/h。 + /// + public double OilMassFlowKgPerHour { get; set; } + + /// + /// 吸气压力,单位 BarA(绝压)。 + /// + public double SuctionPressureBarA { get; set; } + + /// + /// 吸气温度,单位 ℃。 + /// + public double SuctionTemperatureC { get; set; } + + /// + /// 排气压力,单位 BarA(绝压)。 + /// + public double DischargePressureBarA { get; set; } + + /// + /// 排气温度,单位 ℃。 + /// + public double DischargeTemperatureC { get; set; } + + /// + /// 膨胀阀前液路压力,单位 BarA(绝压)。 + /// + public double LiquidPressureBarA { get; set; } + + /// + /// 膨胀阀前液路温度,单位 ℃。 + /// + public double LiquidTemperatureC { get; set; } + + /// + /// 压缩机转速,单位 rpm。 + /// 用于容积效率计算。 + /// + public double CompressorSpeedRpm { get; set; } + + /// + /// 压缩机单转排量,单位 cc。 + /// 用于理论体积流量计算。 + /// + public double CompressorDisplacementCc { get; set; } + } + + /// + /// 六个热力结果值计算结果。 + /// + /// + /// 默认均初始化为 ,用于明确区分“未成功得到结果”和“得到有效数值”。 + /// + public sealed class PPCThermodynamicSixResultsCalculationResult + { + /// + /// 制热量 Qh,单位 kW。 + /// 计算公式:Qh = mRef * (h2 - h3)。 + /// + public double HeatingCapacityQh_kW { get; set; } = double.NaN; + + /// + /// 制冷量 Qc,单位 kW。 + /// 计算公式:Qc = mRef * (h1 - h3)。 + /// + public double CoolingCapacityQc_kW { get; set; } = double.NaN; + + /// + /// 制热 COP。 + /// 计算公式:COPHeating = Qh / W。 + /// + public double COPHeating { get; set; } = double.NaN; + + /// + /// 制冷 COP。 + /// 计算公式:COPCooling = Qc / W。 + /// + public double COPCooling { get; set; } = double.NaN; + + /// + /// 等熵效率,单位 %。 + /// 计算公式:(h2s - h1) / (h2 - h1) * 100。 + /// + public double IsentropicEfficiencyPct { get; set; } = double.NaN; + + /// + /// 容积效率,单位 %。 + /// 计算公式:实际吸气体积流量 / 理论吸气体积流量 * 100。 + /// + public double VolumetricEfficiencyPct { get; set; } = double.NaN; + } + + /// + /// 六个热力结果值独立计算类。 + /// + /// + /// 本类负责以下六个结果的完整计算流程: + /// - 制热量 + /// - COP(制热) + /// - 等熵效率 + /// - 制冷量 + /// - COP(制冷) + /// - 容积效率 + /// + /// 设计目标是把“结果计算逻辑”从 PPCService 中抽离出来, + /// 让服务层只负责取标签和写回,计算类只负责计算本身。 + /// 为降低后续新增其他计算类时对已验算结果的影响范围, + /// 本类将自身所需的底层 REFPROP 支撑实现内聚在类内私有 support 中。 + /// + /// 当前实现严格保持既有流程不变: + /// 1. 由总流量和油流量得到冷媒质量流量 + /// 2. 由吸气点得到 h1 / s1 / v1 + /// 3. 由排气点得到 h2 + /// 4. 由液路点得到 h3 + /// 5. 由排气压力和吸气熵得到 h2s + /// 6. 计算 Qh / Qc / COP / 等熵效率 / 容积效率 + /// + public sealed class PPCThermodynamicSixResultsCalculator + { + /// + /// 底层物性计算支持对象。 + /// 负责提供 REFPROP 初始化与 TPRHO / THERM / PSFLSH 等共用能力。 + /// + private readonly LocalCalculationSupport _support; + + /// + /// 初始化六个热力结果值计算类。 + /// + public PPCThermodynamicSixResultsCalculator() + { + _support = new LocalCalculationSupport(); + } + + /// + /// 按既定流程一次性计算六个热力结果值。 + /// + /// 输入数据,包含功率、流量、吸排气状态点、液路状态点、转速与排量。 + /// 输出结果对象。 + /// + /// 错误或警告信息。 + /// 当前逻辑中,若“容积效率”计算失败但其他 5 个结果成功,会返回 , + /// 同时通过此参数把容积效率失败原因返回给上层,保持原有行为不变。 + /// + /// + /// - :主要结果计算完成;容积效率可能成功也可能失败 + /// - :关键输入或关键热力步骤失败 + /// + public bool TryCalculate(PPCThermodynamicSixResultsCalculationInput input, out PPCThermodynamicSixResultsCalculationResult result, out string error) + { + // 创建输出对象,并将错误文本清空。 + result = new PPCThermodynamicSixResultsCalculationResult(); + error = string.Empty; + + // 所有物性函数调用之前,先确保 REFPROP 已完成初始化。 + if (!_support.EnsureRefpropInitialized(out var initErr)) + { + error = initErr; + return false; + } + + // 第 1 步:将压缩机输入功率由 W 换算为 kW。 + if (!TryGetCompressorPower_kW(input.CompressorPowerW, out var w_kW, out var wErr)) + { + error = wErr; + return false; + } + + // 第 2 步:根据“总流量 - 油流量”,得到真正参与循环的冷媒质量流量, + // 并从 kg/h 换算到后续公式使用的 kg/s。 + if (!TryGetRefrigerantMassFlow_kg_s(input.TotalMassFlowKgPerHour, input.OilMassFlowKgPerHour, out var mRef_kg_s, out var mRefErr)) + { + error = mRefErr; + return false; + } + + // 第 3 步:由吸气压力/温度求出吸气点状态: + // - h1:吸气比焓 + // - s1:吸气比熵 + // - v1:吸气比容 + if (!TryGetVaporPointState_ByTP_BarA_C(input.SuctionPressureBarA, input.SuctionTemperatureC, out var h1_kJkg, out var s1_kJkgK, out var v1_m3kg, out var p1Err)) + { + error = $"h1/s1/吸气比容计算失败: {p1Err}"; + return false; + } + + // 第 4 步:由排气压力/温度求排气比焓 h2。 + if (!TryGetVaporPointEnthalpy_ByTP_BarA_C(input.DischargePressureBarA, input.DischargeTemperatureC, out var h2_kJkg, out var p2Err)) + { + error = $"h2 计算失败: {p2Err}"; + return false; + } + + // 第 5 步:由液路压力/温度求液路比焓 h3。 + if (!TryGetLiquidPointEnthalpy_ByTP_BarA_C(input.LiquidPressureBarA, input.LiquidTemperatureC, out var h3_kJkg, out var p3Err)) + { + error = $"h3 计算失败: {p3Err}"; + return false; + } + + // 第 6 步:由排气压力 P2 和吸气熵 s1 求等熵出口焓 h2s。 + if (!_support.TryGetIsentropicOutletEnthalpy_h2s_ByP2AndS1_BarA(input.DischargePressureBarA, s1_kJkgK, out var h2s_kJkg, out var h2sErr)) + { + error = $"h2s 计算失败: {h2sErr}"; + return false; + } + + // 第 7 步:计算 Qh、Qc、COP(制热)、COP(制冷)。 + if (!TryComputeCapacitiesAndCOP(mRef_kg_s, h1_kJkg, h2_kJkg, h3_kJkg, w_kW, out var qh_kW, out var qc_kW, out var copH, out var copC, out var capErr)) + { + error = capErr; + return false; + } + + // 第 8 步:计算等熵效率。 + if (!TryComputeIsentropicEfficiencyPct(h1_kJkg, h2_kJkg, h2s_kJkg, out var etaS_pct, out var etaSErr)) + { + error = etaSErr; + return false; + } + + // 先写入 5 个关键结果。 + result.HeatingCapacityQh_kW = qh_kW; + result.CoolingCapacityQc_kW = qc_kW; + result.COPHeating = copH; + result.COPCooling = copC; + result.IsentropicEfficiencyPct = etaS_pct; + + // 最后再算容积效率。 + // 当前逻辑特意保持与旧实现一致: + // 即使容积效率失败,只要前面 5 个主结果已经成功,就仍然返回 true, + // 同时把失败原因放到 error 中,供调用方决定是否记录为警告。 + if (!TryComputeVolumetricEfficiencyPct(mRef_kg_s, v1_m3kg, input.CompressorSpeedRpm, input.CompressorDisplacementCc, out var etaV_pct, out var etaVErr)) + { + error = etaVErr; + return true; + } + + result.VolumetricEfficiencyPct = etaV_pct; + return true; + } + + /// + /// 获取压缩机输入功率并换算为 kW。 + /// + /// 输入功率,单位 W。 + /// 输出功率,单位 kW。 + /// 失败原因。 + /// 是否换算成功。 + private bool TryGetCompressorPower_kW(double compressorPowerW, out double w_kW, out string error) + { + w_kW = double.NaN; + error = string.Empty; + + // 读取输入功率,并做基础合法性校验。 + double w_W = compressorPowerW; + if (double.IsNaN(w_W) || double.IsInfinity(w_W) || w_W <= 0) + { + error = $"无效压缩机功率 HV[W]={w_W}"; + return false; + } + + // 六个热力结果公式里统一使用 kW,因此这里做 W -> kW 的标准换算。 + w_kW = w_W / 1000.0; + return true; + } + + /// + /// 获取冷媒质量流量,输出单位 kg/s。 + /// + /// 总流量,单位 kg/h。 + /// 油流量,单位 kg/h。 + /// 冷媒质量流量输出,单位 kg/s。 + /// 失败原因。 + /// 是否计算成功。 + /// + /// 当前流程保持与图片/旧代码一致: + /// 1. 读取总流量 kg/h + /// 2. 读取油流量 kg/h + /// 3. 冷媒流量 = 总流量 - 油流量 + /// 4. 再由 kg/h 换算为 kg/s + /// + private bool TryGetRefrigerantMassFlow_kg_s(double totalMassFlowKgPerHour, double oilMassFlowKgPerHour, out double mRef_kg_s, out string error) + { + mRef_kg_s = double.NaN; + error = string.Empty; + + // 先读取总流量。 + if (!TryGetTotalMassFlow_kg_h(totalMassFlowKgPerHour, out var mTotal_kg_h, out var totalErr)) + { + error = totalErr; + return false; + } + + // 再读取油流量。 + if (!TryGetOilMassFlow_kg_h(oilMassFlowKgPerHour, out var mOil_kg_h, out var oilErr)) + { + error = oilErr; + return false; + } + + // 计算真正参与循环的冷媒质量流量:mRef = mTotal - mOil。 + if (!TryComputeRefrigerantMassFlow_kg_h(mTotal_kg_h, mOil_kg_h, out var mRef_kg_h, out var refErr)) + { + error = refErr; + return false; + } + + // 将 kg/h 换算成后续容量计算使用的 kg/s。 + return TryConvertMassFlow_kg_h_To_kg_s(mRef_kg_h, out mRef_kg_s, out error); + } + + /// + /// 获取总流量,单位 kg/h。 + /// + /// 输入总流量。 + /// 输出总流量。 + /// 失败原因。 + /// 是否读取成功。 + private bool TryGetTotalMassFlow_kg_h(double totalMassFlowKgPerHour, out double mTotal_kg_h, out string error) + { + mTotal_kg_h = double.NaN; + error = string.Empty; + + // 当前方法只做数值读取和合法性校验,不做其他换算。 + mTotal_kg_h = totalMassFlowKgPerHour; + if (double.IsNaN(mTotal_kg_h) || double.IsInfinity(mTotal_kg_h)) + { + error = "总流量(冷媒流量)为 NaN/Inf"; + return false; + } + + return true; + } + + /// + /// 获取油流量,单位 kg/h。 + /// + /// 输入油流量。 + /// 输出油流量。 + /// 失败原因。 + /// 是否读取成功。 + private bool TryGetOilMassFlow_kg_h(double oilMassFlowKgPerHour, out double mOil_kg_h, out string error) + { + mOil_kg_h = double.NaN; + error = string.Empty; + + // 当前方法只做数值读取和合法性校验,不做其他换算。 + mOil_kg_h = oilMassFlowKgPerHour; + if (double.IsNaN(mOil_kg_h) || double.IsInfinity(mOil_kg_h)) + { + error = "油流量为 NaN/Inf"; + return false; + } + + return true; + } + + /// + /// 计算冷媒质量流量,单位 kg/h。 + /// + /// 总流量,单位 kg/h。 + /// 油流量,单位 kg/h。 + /// 冷媒质量流量输出,单位 kg/h。 + /// 失败原因。 + /// 是否计算成功。 + private bool TryComputeRefrigerantMassFlow_kg_h(double mTotal_kg_h, double mOil_kg_h, out double mRef_kg_h, out string error) + { + mRef_kg_h = double.NaN; + error = string.Empty; + + if (double.IsNaN(mTotal_kg_h) || double.IsInfinity(mTotal_kg_h)) + { + error = "总流量(冷媒流量)为 NaN/Inf"; + return false; + } + + if (double.IsNaN(mOil_kg_h) || double.IsInfinity(mOil_kg_h)) + { + error = "油流量为 NaN/Inf"; + return false; + } + + // 与既有实现一致:冷媒质量流量 = 总流量 - 油流量。 + mRef_kg_h = mTotal_kg_h - mOil_kg_h; + if (mRef_kg_h <= 0) + { + error = $"冷媒质量流量<=0,总流量={mTotal_kg_h}kg/h,油流量={mOil_kg_h}kg/h"; + return false; + } + + return true; + } + + /// + /// 将质量流量从 kg/h 换算到 kg/s。 + /// + /// 输入质量流量,单位 kg/h。 + /// 输出质量流量,单位 kg/s。 + /// 失败原因。 + /// 是否换算成功。 + private bool TryConvertMassFlow_kg_h_To_kg_s(double massFlow_kg_h, out double massFlow_kg_s, out string error) + { + massFlow_kg_s = double.NaN; + error = string.Empty; + + if (double.IsNaN(massFlow_kg_h) || double.IsInfinity(massFlow_kg_h) || massFlow_kg_h <= 0) + { + error = $"无效质量流量={massFlow_kg_h}kg/h"; + return false; + } + + // 1 小时 = 3600 秒,因此 kg/h -> kg/s 需要除以 3600。 + massFlow_kg_s = massFlow_kg_h / 3600.0; + return true; + } + + /// + /// 由某一气相状态点的压力 / 温度求质量比焓。 + /// + /// 输入压力,单位 BarA(绝压)。 + /// 输入温度,单位 ℃。 + /// 输出质量比焓,单位 kJ/kg。 + /// 失败原因。 + /// 是否计算成功。 + /// + /// 当前流程: + /// 1. BarA -> MPa + /// 2. 用气相 TPRHO 求摩尔密度 D + /// 3. 用 THERM 求焓 + /// + private bool TryGetVaporPointEnthalpy_ByTP_BarA_C(double pressureBarA, double temperatureC, out double h_kJkg, out string error) + { + h_kJkg = double.NaN; + error = string.Empty; + + // 物性 helper 的压力输入单位是 MPa,因此先换算。 + double pMPa = pressureBarA * 0.1; + + // 先求气相摩尔密度 D。 + if (!_support.TryTPRHO_VaporDensity_ByTP_MPa_C(pMPa, temperatureC, out var d_molL, out var dErr)) + { + error = dErr; + return false; + } + + // THERM helper 的温度输入为 K,因此把 ℃ 转成 K 后再求焓。 + double tK = temperatureC + 273.15; + if (!_support.TryTHERM_Enthalpy_kJkg_ByT_K_D(tK, d_molL, out h_kJkg, out var hErr)) + { + error = hErr; + return false; + } + + return true; + } + + /// + /// 由某一液相状态点的压力 / 温度求质量比焓。 + /// + /// 输入压力,单位 BarA(绝压)。 + /// 输入温度,单位 ℃。 + /// 输出质量比焓,单位 kJ/kg。 + /// 失败原因。 + /// 是否计算成功。 + private bool TryGetLiquidPointEnthalpy_ByTP_BarA_C(double pressureBarA, double temperatureC, out double h_kJkg, out string error) + { + h_kJkg = double.NaN; + error = string.Empty; + + // 物性 helper 的压力输入单位是 MPa,因此先换算。 + double pMPa = pressureBarA * 0.1; + + // 先按液相路径求摩尔密度 D。 + if (!_support.TryTPRHO_LiquidDensity_ByTP_MPa_C(pMPa, temperatureC, out var d_molL, out var dErr)) + { + error = dErr; + return false; + } + + // 再按液相路径求质量比焓 h。 + if (!_support.TryTHERM_LiquidEnthalpy_ByTD(temperatureC, d_molL, out h_kJkg, out var hErr)) + { + error = hErr; + return false; + } + + return true; + } + + /// + /// 由某一气相状态点的压力 / 温度联合求取 h、s、v。 + /// + /// 输入压力,单位 BarA(绝压)。 + /// 输入温度,单位 ℃。 + /// 输出质量比焓,单位 kJ/kg。 + /// 输出质量比熵,单位 kJ/(kg·K)。 + /// 输出比容,单位 m³/kg。 + /// 失败原因。 + /// 是否计算成功。 + private bool TryGetVaporPointState_ByTP_BarA_C(double pressureBarA, double temperatureC, out double h_kJkg, out double s_kJkgK, out double v_m3kg, out string error) + { + h_kJkg = double.NaN; + s_kJkgK = double.NaN; + v_m3kg = double.NaN; + error = string.Empty; + + // 第一步:BarA -> MPa。 + double pMPa = pressureBarA * 0.1; + + // 第二步:由气相 TPRHO 求摩尔密度 D。 + if (!_support.TryTPRHO_VaporDensity_ByTP_MPa_C(pMPa, temperatureC, out var d_molL, out var dErr)) + { + error = dErr; + return false; + } + + // 第三步:用 THERM 求吸气点比焓 h1。 + double tK = temperatureC + 273.15; + if (!_support.TryTHERM_Enthalpy_kJkg_ByT_K_D(tK, d_molL, out h_kJkg, out var hErr)) + { + error = hErr; + return false; + } + + // 第四步:用 THERM 求吸气点比熵 s1。 + if (!_support.TryTHERM_VaporEntropy_ByTD(temperatureC, d_molL, out s_kJkgK, out var sErr)) + { + error = sErr; + return false; + } + + // 第五步:由摩尔密度换算为质量比容 v1。 + if (!_support.TryConvertMolarDensityToSpecificVolume(d_molL, out v_m3kg, out var vErr)) + { + error = vErr; + return false; + } + + return true; + } + + /// + /// 统一计算制热量、制冷量、制热 COP、制冷 COP。 + /// + /// 冷媒质量流量,单位 kg/s。 + /// 吸气比焓,单位 kJ/kg。 + /// 排气比焓,单位 kJ/kg。 + /// 液路比焓,单位 kJ/kg。 + /// 压缩机功率,单位 kW。 + /// 制热量输出,单位 kW。 + /// 制冷量输出,单位 kW。 + /// 制热 COP 输出。 + /// 制冷 COP 输出。 + /// 失败原因。 + /// 是否计算成功。 + private bool TryComputeCapacitiesAndCOP(double mRef_kg_s, double h1_kJkg, double h2_kJkg, double h3_kJkg, double w_kW, + out double qh_kW, out double qc_kW, out double copHeating, out double copCooling, out string error) + { + qh_kW = double.NaN; + qc_kW = double.NaN; + copHeating = double.NaN; + copCooling = double.NaN; + error = string.Empty; + + // 先算制热量:Qh = mRef * (h2 - h3)。 + if (!TryComputeHeatingCapacityQh_kW(mRef_kg_s, h2_kJkg, h3_kJkg, out qh_kW, out var qhErr)) + { + error = qhErr; + return false; + } + + // 再算制冷量:Qc = mRef * (h1 - h3)。 + if (!TryComputeCoolingCapacityQc_kW(mRef_kg_s, h1_kJkg, h3_kJkg, out qc_kW, out var qcErr)) + { + error = qcErr; + return false; + } + + // 制热 COP = Qh / W。 + if (!TryComputeHeatingCOP(qh_kW, w_kW, out copHeating, out var copHErr)) + { + error = copHErr; + return false; + } + + // 制冷 COP = Qc / W。 + if (!TryComputeCoolingCOP(qc_kW, w_kW, out copCooling, out var copCErr)) + { + error = copCErr; + return false; + } + + return true; + } + + /// + /// 计算焓差,单位 kJ/kg。 + /// + /// 被减数,单位 kJ/kg。 + /// 减数,单位 kJ/kg。 + /// 当前计算量名称,用于拼接错误信息。 + /// 焓差输出,单位 kJ/kg。 + /// 失败原因。 + /// 是否计算成功。 + private bool TryComputeEnthalpyDifference_kJkg(double minuend_kJkg, double subtrahend_kJkg, string quantityName, out double deltaH_kJkg, out string error) + { + deltaH_kJkg = double.NaN; + error = string.Empty; + + // 先校验输入两端焓值是否有效。 + if (double.IsNaN(minuend_kJkg) || double.IsInfinity(minuend_kJkg) || double.IsNaN(subtrahend_kJkg) || double.IsInfinity(subtrahend_kJkg)) + { + error = $"{quantityName}输入存在 NaN/Inf"; + return false; + } + + // 焓差按“被减数 - 减数”计算。 + deltaH_kJkg = minuend_kJkg - subtrahend_kJkg; + if (double.IsNaN(deltaH_kJkg) || double.IsInfinity(deltaH_kJkg)) + { + error = $"{quantityName}结果异常"; + return false; + } + + return true; + } + + /// + /// 根据质量流量与焓差计算容量,单位 kW。 + /// + /// 冷媒质量流量,单位 kg/s。 + /// 焓差,单位 kJ/kg。 + /// 容量名称,用于错误信息。 + /// 容量输出,单位 kW。 + /// 失败原因。 + /// 是否计算成功。 + /// + /// 公式:Capacity = mRef * Δh。 + /// 因为 kg/s * kJ/kg = kJ/s = kW,所以结果天然就是 kW。 + /// + private bool TryComputeCapacity_kW(double mRef_kg_s, double deltaH_kJkg, string quantityName, out double capacity_kW, out string error) + { + capacity_kW = double.NaN; + error = string.Empty; + + // 校验冷媒质量流量是否有效。 + if (double.IsNaN(mRef_kg_s) || double.IsInfinity(mRef_kg_s) || mRef_kg_s <= 0) + { + error = "无效冷媒质量流量"; + return false; + } + + // 校验焓差是否有效。 + if (double.IsNaN(deltaH_kJkg) || double.IsInfinity(deltaH_kJkg)) + { + error = $"{quantityName}焓差异常"; + return false; + } + + // 按容量公式直接计算。 + capacity_kW = mRef_kg_s * deltaH_kJkg; + if (double.IsNaN(capacity_kW) || double.IsInfinity(capacity_kW)) + { + error = $"{quantityName}结果异常"; + return false; + } + + return true; + } + + /// + /// 计算制热量 Qh,单位 kW。 + /// + /// 冷媒质量流量,单位 kg/s。 + /// 排气比焓 h2,单位 kJ/kg。 + /// 液路比焓 h3,单位 kJ/kg。 + /// 制热量输出,单位 kW。 + /// 失败原因。 + /// 是否计算成功。 + private bool TryComputeHeatingCapacityQh_kW(double mRef_kg_s, double h2_kJkg, double h3_kJkg, out double qh_kW, out string error) + { + qh_kW = double.NaN; + error = string.Empty; + + // 先计算制热焓差:(h2 - h3)。 + if (!TryComputeEnthalpyDifference_kJkg(h2_kJkg, h3_kJkg, "制热焓差(h2-h3)", out var deltaH_kJkg, out var deltaErr)) + { + error = deltaErr; + return false; + } + + // 再由质量流量与焓差得到制热量。 + return TryComputeCapacity_kW(mRef_kg_s, deltaH_kJkg, "制热量Qh", out qh_kW, out error); + } + + /// + /// 计算制冷量 Qc,单位 kW。 + /// + /// 冷媒质量流量,单位 kg/s。 + /// 吸气比焓 h1,单位 kJ/kg。 + /// 液路比焓 h3,单位 kJ/kg。 + /// 制冷量输出,单位 kW。 + /// 失败原因。 + /// 是否计算成功。 + private bool TryComputeCoolingCapacityQc_kW(double mRef_kg_s, double h1_kJkg, double h3_kJkg, out double qc_kW, out string error) + { + qc_kW = double.NaN; + error = string.Empty; + + // 先计算制冷焓差:(h1 - h3)。 + if (!TryComputeEnthalpyDifference_kJkg(h1_kJkg, h3_kJkg, "制冷焓差(h1-h3)", out var deltaH_kJkg, out var deltaErr)) + { + error = deltaErr; + return false; + } + + // 再由质量流量与焓差得到制冷量。 + return TryComputeCapacity_kW(mRef_kg_s, deltaH_kJkg, "制冷量Qc", out qc_kW, out error); + } + + /// + /// 计算 COP。 + /// + /// 容量,单位 kW。 + /// 压缩机输入功率,单位 kW。 + /// 当前 COP 名称,用于错误信息。 + /// COP 输出。 + /// 失败原因。 + /// 是否计算成功。 + private bool TryComputeCOP(double capacity_kW, double w_kW, string quantityName, out double cop, out string error) + { + cop = double.NaN; + error = string.Empty; + + // 容量必须是有效数值。 + if (double.IsNaN(capacity_kW) || double.IsInfinity(capacity_kW)) + { + error = $"{quantityName}输入异常"; + return false; + } + + // 功率必须有效且大于 0,避免除零。 + if (double.IsNaN(w_kW) || double.IsInfinity(w_kW) || w_kW <= 0) + { + error = "无效压缩机功率"; + return false; + } + + // COP = 容量 / 输入功率。 + cop = capacity_kW / w_kW; + if (double.IsNaN(cop) || double.IsInfinity(cop)) + { + error = $"{quantityName}结果异常"; + return false; + } + + return true; + } + + /// + /// 计算制热 COP。 + /// + /// 制热量,单位 kW。 + /// 压缩机输入功率,单位 kW。 + /// 制热 COP 输出。 + /// 失败原因。 + /// 是否计算成功。 + private bool TryComputeHeatingCOP(double qh_kW, double w_kW, out double copHeating, out string error) + { + return TryComputeCOP(qh_kW, w_kW, "COP(制热)", out copHeating, out error); + } + + /// + /// 计算制冷 COP。 + /// + /// 制冷量,单位 kW。 + /// 压缩机输入功率,单位 kW。 + /// 制冷 COP 输出。 + /// 失败原因。 + /// 是否计算成功。 + private bool TryComputeCoolingCOP(double qc_kW, double w_kW, out double copCooling, out string error) + { + return TryComputeCOP(qc_kW, w_kW, "COP(制冷)", out copCooling, out error); + } + + /// + /// 计算等熵效率,单位 %。 + /// + /// 吸气比焓 h1,单位 kJ/kg。 + /// 实际排气比焓 h2,单位 kJ/kg。 + /// 等熵出口比焓 h2s,单位 kJ/kg。 + /// 等熵效率输出,单位 %。 + /// 失败原因。 + /// 是否计算成功。 + private bool TryComputeIsentropicEfficiencyPct(double h1_kJkg, double h2_kJkg, double h2s_kJkg, out double etaS_pct, out string error) + { + etaS_pct = double.NaN; + error = string.Empty; + + // 实际压缩焓升:(h2 - h1)。 + if (!TryComputeEnthalpyDifference_kJkg(h2_kJkg, h1_kJkg, "实际压缩焓升(h2-h1)", out var actualRise_kJkg, out var actualErr)) + { + error = actualErr; + return false; + } + + // 等熵压缩焓升:(h2s - h1)。 + if (!TryComputeEnthalpyDifference_kJkg(h2s_kJkg, h1_kJkg, "等熵压缩焓升(h2s-h1)", out var isentropicRise_kJkg, out var isoErr)) + { + error = isoErr; + return false; + } + + // 等熵效率 = 等熵焓升 / 实际焓升 * 100%。 + return TryComputeEfficiencyPct(isentropicRise_kJkg, actualRise_kJkg, "等熵效率", out etaS_pct, out error); + } + + /// + /// 计算容积效率,单位 %。 + /// + /// 冷媒质量流量,单位 kg/s。 + /// 吸气比容 v1,单位 m³/kg。 + /// 压缩机转速,单位 rpm。 + /// 压缩机排量,单位 cc。 + /// 容积效率输出,单位 %。 + /// 失败原因。 + /// 是否计算成功。 + private bool TryComputeVolumetricEfficiencyPct(double mRef_kg_s, double v1_m3kg, double speed_rpm, double disp_cc, out double etaV_pct, out string error) + { + etaV_pct = double.NaN; + error = string.Empty; + + // 先校验转速。 + if (!TryGetCompressorSpeed_rpm(speed_rpm, out var validatedSpeed_rpm, out var speedErr)) + { + error = speedErr; + return false; + } + + // 再校验排量。 + if (!TryGetCompressorDisplacement_cc(disp_cc, out var validatedDisp_cc, out var dispErr)) + { + error = dispErr; + return false; + } + + // 计算实际吸气体积流量。 + if (!TryComputeSuctionVolumeFlow_m3_h(mRef_kg_s, v1_m3kg, out var suctionVolFlow_m3_h, out var suctionErr)) + { + error = suctionErr; + return false; + } + + // 计算理论吸气体积流量。 + if (!TryComputeTheoreticalVolumeFlow_m3_h(validatedSpeed_rpm, validatedDisp_cc, out var theoVolFlow_m3_h, out var theoErr)) + { + error = theoErr; + return false; + } + + // 容积效率 = 实际吸气体积流量 / 理论吸气体积流量 * 100%。 + return TryComputeEfficiencyPct(suctionVolFlow_m3_h, theoVolFlow_m3_h, "容积效率", out etaV_pct, out error); + } + + /// + /// 计算百分比效率。 + /// + /// 分子。 + /// 分母。 + /// 当前效率名称,用于错误信息。 + /// 效率输出,单位 %。 + /// 失败原因。 + /// 是否计算成功。 + private bool TryComputeEfficiencyPct(double numerator, double denominator, string quantityName, out double efficiencyPct, out string error) + { + efficiencyPct = double.NaN; + error = string.Empty; + + // 先校验分子。 + if (double.IsNaN(numerator) || double.IsInfinity(numerator)) + { + error = $"{quantityName}分子异常"; + return false; + } + + // 再校验分母,避免除零或极小数造成结果发散。 + const double eps = 1e-9; + if (double.IsNaN(denominator) || double.IsInfinity(denominator) || Math.Abs(denominator) < eps) + { + error = $"{quantityName}分母过小或异常"; + return false; + } + + // 效率 = 分子 / 分母。 + double efficiency = numerator / denominator; + if (double.IsNaN(efficiency) || double.IsInfinity(efficiency)) + { + error = $"{quantityName}结果异常"; + return false; + } + + // 将无量纲效率换算为百分数。 + efficiencyPct = efficiency * 100.0; + return true; + } + + /// + /// 获取并校验压缩机转速,单位 rpm。 + /// + /// 输入转速,单位 rpm。 + /// 输出转速,单位 rpm。 + /// 失败原因。 + /// 是否获取成功。 + private bool TryGetCompressorSpeed_rpm(double compressorSpeedRpm, out double speed_rpm, out string error) + { + speed_rpm = double.NaN; + error = string.Empty; + + // 当前方法只做读取与合法性校验。 + speed_rpm = compressorSpeedRpm; + if (double.IsNaN(speed_rpm) || double.IsInfinity(speed_rpm) || speed_rpm <= 0) + { + error = $"无效转速: {speed_rpm} rpm"; + return false; + } + + return true; + } + + /// + /// 计算实际吸气体积流量,单位 m³/h。 + /// + /// 冷媒质量流量,单位 kg/s。 + /// 吸气比容,单位 m³/kg。 + /// 实际吸气体积流量输出,单位 m³/h。 + /// 失败原因。 + /// 是否计算成功。 + private bool TryComputeSuctionVolumeFlow_m3_h(double mRef_kg_s, double v1_m3kg, out double suctionVolFlow_m3_h, out string error) + { + suctionVolFlow_m3_h = double.NaN; + error = string.Empty; + + // 校验质量流量。 + if (double.IsNaN(mRef_kg_s) || double.IsInfinity(mRef_kg_s) || mRef_kg_s <= 0) + { + error = "无效冷媒质量流量"; + return false; + } + + // 校验吸气比容。 + if (double.IsNaN(v1_m3kg) || double.IsInfinity(v1_m3kg) || v1_m3kg <= 0) + { + error = "无效吸气比容"; + return false; + } + + // 实际吸气体积流量 = (kg/s * 3600) * m³/kg = m³/h。 + suctionVolFlow_m3_h = (mRef_kg_s * 3600.0) * v1_m3kg; + if (double.IsNaN(suctionVolFlow_m3_h) || double.IsInfinity(suctionVolFlow_m3_h)) + { + error = "实际吸气体积流量结果异常"; + return false; + } + + return true; + } + + /// + /// 计算理论吸气体积流量,单位 m³/h。 + /// + /// 压缩机转速,单位 rpm。 + /// 压缩机排量,单位 cc。 + /// 理论吸气体积流量输出,单位 m³/h。 + /// 失败原因。 + /// 是否计算成功。 + /// + /// 当前公式保持原实现不变: + /// (speed_rpm / 60.0) * disp_cc * 0.0036 + /// + private bool TryComputeTheoreticalVolumeFlow_m3_h(double speed_rpm, double disp_cc, out double theoVolFlow_m3_h, out string error) + { + theoVolFlow_m3_h = double.NaN; + error = string.Empty; + + if (double.IsNaN(speed_rpm) || double.IsInfinity(speed_rpm) || speed_rpm <= 0) + { + error = $"无效转速: {speed_rpm} rpm"; + return false; + } + + if (double.IsNaN(disp_cc) || double.IsInfinity(disp_cc) || disp_cc <= 0) + { + error = $"无效压缩机排量: {disp_cc} cc"; + return false; + } + + // 理论体积流量公式保持原有写法不变。 + theoVolFlow_m3_h = (speed_rpm / 60.0) * disp_cc * 0.0036; + if (double.IsNaN(theoVolFlow_m3_h) || double.IsInfinity(theoVolFlow_m3_h) || theoVolFlow_m3_h <= 0) + { + error = "理论吸气体积流量<=0"; + return false; + } + + return true; + } + + /// + /// 获取并校验压缩机排量,单位 cc。 + /// + /// 输入排量,单位 cc。 + /// 输出排量,单位 cc。 + /// 失败原因。 + /// 是否获取成功。 + private bool TryGetCompressorDisplacement_cc(double compressorDisplacementCc, out double displacement_cc, out string error) + { + displacement_cc = double.NaN; + error = string.Empty; + + // 当前方法只做读取与合法性校验。 + displacement_cc = compressorDisplacementCc; + if (double.IsNaN(displacement_cc) || double.IsInfinity(displacement_cc) || displacement_cc <= 0) + { + error = $"压缩机排量<=0: {displacement_cc}"; + return false; + } + + return true; + } + /// + /// 六个热力结果计算类私有的底层物性支持实现。 + /// + /// + /// 该实现仅服务当前六结果计算类,不与其他结果类共享实现细节, + /// 这样即使后续为了新计算类调整别处的 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) => throw new NotSupportedException(); + + public bool TryTPRHO_VaporDensity_ByTP_MPa_C(double pressureMPa, double temperatureC, out double densityMolPerL, out string error) + { + densityMolPerL = double.NaN; + error = string.Empty; + + double tK = temperatureC + 273.15; + double pKPa = pressureMPa * 1000.0; + + double[] x = new double[20]; + x[0] = 1.0; + + long kph = 2; + long kguess = 0; + double D = 0.0; + long ierr = 0; + long herrLen = 255; + string herr = new string(' ', 255); + + lock (_refpropLock) + { + IRefProp64.TPRHOdll(ref tK, ref pKPa, x, ref kph, ref kguess, ref D, ref ierr, ref herr, ref herrLen); + } + + if (ierr != 0) + { + error = $"TPRHO 错误: {herr.Trim()} (ierr={ierr})"; + return false; + } + + densityMolPerL = D; + return true; + } + + public bool TryTHERM_VaporEntropy_ByTD(double temperatureC, double densityMolPerL, out double entropy_kJ_per_kgK, out string error) + { + entropy_kJ_per_kgK = double.NaN; + error = string.Empty; + + double tK = temperatureC + 273.15; + double D = densityMolPerL; + + double[] x = new double[20]; + x[0] = 1.0; + + double pOut = 0; + double e = 0; + double hJmol = 0; + double sJmolK = 0; + double cv = 0; + double cp = 0; + double w = 0; + double hjt = 0; + + double molarMassKgPerMol = GetMolarMass(); + if (molarMassKgPerMol <= 0) + { + error = "无效的摩尔质量"; + return false; + } + + lock (_refpropLock) + { + IRefProp64.THERMdll(ref tK, ref D, x, ref pOut, ref e, ref hJmol, ref sJmolK, ref cv, ref cp, ref w, ref hjt); + } + + entropy_kJ_per_kgK = (sJmolK / molarMassKgPerMol) * 0.001; + return true; + } + + public bool TryTPRHO_LiquidDensity_ByTP_MPa_C(double pressureMPa, double temperatureC, out double densityMolPerL, out string error) + { + densityMolPerL = double.NaN; + error = string.Empty; + + double tK = temperatureC + 273.15; + double pKPa = pressureMPa * 1000.0; + + double[] x = new double[20]; + x[0] = 1.0; + + long kph = 1; + long kguess = 0; + double D = 0.0; + long ierr = 0; + long herrLen = 255; + string herr = new string(' ', 255); + + lock (_refpropLock) + { + IRefProp64.TPRHOdll(ref tK, ref pKPa, x, ref kph, ref kguess, ref D, ref ierr, ref herr, ref herrLen); + } + + if (ierr != 0) + { + error = $"TPRHO(液相) 错误: {herr.Trim()} (ierr={ierr})"; + return false; + } + + densityMolPerL = D; + return true; + } + + public bool TryTHERM_LiquidEnthalpy_ByTD(double temperatureC, double densityMolPerL, out double h_liq_kJ_per_kg, out string error) + { + h_liq_kJ_per_kg = double.NaN; + error = string.Empty; + + double tK = temperatureC + 273.15; + double D = densityMolPerL; + + double[] x = new double[20]; + x[0] = 1.0; + + double pOut = 0; + double e = 0; + double hJmol = 0; + double sJmolK = 0; + double cv = 0; + double cp = 0; + double w = 0; + double hjt = 0; + + double molarMassKgPerMol = GetMolarMass(); + if (molarMassKgPerMol <= 0) + { + error = "无效的摩尔质量"; + return false; + } + + lock (_refpropLock) + { + IRefProp64.THERMdll(ref tK, ref D, x, ref pOut, ref e, ref hJmol, ref sJmolK, ref cv, ref cp, ref w, ref hjt); + } + + h_liq_kJ_per_kg = (hJmol / molarMassKgPerMol) * 0.001; + return true; + } + + public bool TryTHERM_Enthalpy_kJkg_ByT_K_D(double temperatureK, double densityMolPerL, out double h_kJ_per_kg, out string error) + { + h_kJ_per_kg = double.NaN; + error = string.Empty; + + double tK = temperatureK; + double D = densityMolPerL; + + double[] x = new double[20]; + x[0] = 1.0; + + double pOut = 0; + double e = 0; + double hJmol = 0; + double sJmolK = 0; + double cv = 0; + double cp = 0; + double w = 0; + double hjt = 0; + + double molarMassKgPerMol = GetMolarMass(); + if (molarMassKgPerMol <= 0) + { + error = "无效的摩尔质量"; + return false; + } + + lock (_refpropLock) + { + IRefProp64.THERMdll(ref tK, ref D, x, ref pOut, ref e, ref hJmol, ref sJmolK, ref cv, ref cp, ref w, ref hjt); + } + + h_kJ_per_kg = (hJmol / molarMassKgPerMol) * 0.001; + return true; + } + + public bool TryConvertMolarDensityToSpecificVolume(double d_molL, out double v_m3kg, out string error) + { + v_m3kg = double.NaN; + error = string.Empty; + + if (double.IsNaN(d_molL) || double.IsInfinity(d_molL) || d_molL <= 0) + { + error = $"无效摩尔密度: {d_molL} mol/L"; + return false; + } + + double molarMassKgPerMol = GetMolarMass(); + if (molarMassKgPerMol <= 0) + { + error = "无效的摩尔质量"; + return false; + } + + double rho_kg_m3 = d_molL * molarMassKgPerMol * 1000.0; + if (rho_kg_m3 <= 0) + { + error = $"无效密度: {rho_kg_m3} kg/m3"; + return false; + } + + v_m3kg = 1.0 / rho_kg_m3; + return true; + } + + public bool TryGetIsentropicOutletEnthalpy_h2s_ByP2AndS1_BarA(double dischargePressureBarA, double suctionEntropy_kJkgK, out double h2s_kJkg, out string error) + { + h2s_kJkg = double.NaN; + error = string.Empty; + + double pKPa = dischargePressureBarA * 100.0; + if (pKPa <= 0) + { + error = $"无效排气压力: {dischargePressureBarA} BarA"; + return false; + } + + if (double.IsNaN(suctionEntropy_kJkgK) || double.IsInfinity(suctionEntropy_kJkgK)) + { + error = "无效吸气熵 s1"; + return false; + } + + if (!TryConvertS_kJkgK_To_JmolK(suctionEntropy_kJkgK, out var s_JmolK, out var convErr)) + { + error = convErr; + return false; + } + + double[] z = new double[20]; + z[0] = 1.0; + + double t = 0.0; + double d = 0.0; + double Dl = 0.0; + double Dv = 0.0; + double[] xliq = new double[20]; + double[] xvap = new double[20]; + double q = 0.0; + double ee = 0.0; + double h = 0.0; + double Cv = 0.0; + double Cp = 0.0; + double w = 0.0; + long ierr = 0; + long herrLen = 255; + string herr = new string(' ', 255); + + lock (_refpropLock) + { + IRefProp64.PSFLSHdll(ref pKPa, ref s_JmolK, z, ref t, ref d, ref Dl, ref Dv, xliq, xvap, ref q, ref ee, ref h, ref Cv, ref Cp, ref w, ref ierr, ref herr, ref herrLen); + } + + if (ierr != 0) + { + error = $"PSFLSH 错误: {herr.Trim()} (ierr={ierr})"; + return false; + } + + return TryConvertH_Jmol_To_kJkg(h, out h2s_kJkg, out error); + } + + private static double GetMolarMass() + { + double wmm = 0; + double Trp = 0; + double Tnbpt = 0; + double Tc = 0; + double Pc = 0; + double Dc = 0; + double Zc = 0; + double acf = 0; + double dip = 0; + double Rgas = 0; + long componentId = 1; + + IRefProp64.INFOdll(ref componentId, ref wmm, ref Trp, ref Tnbpt, ref Tc, ref Pc, ref Dc, ref Zc, ref acf, ref dip, ref Rgas); + return wmm * 0.001; + } + + private static bool TryConvertH_Jmol_To_kJkg(double h_Jmol, out double h_kJkg, out string error) + { + h_kJkg = double.NaN; + error = string.Empty; + + double molarMassKgPerMol = GetMolarMass(); + if (molarMassKgPerMol <= 0) + { + error = "无效的摩尔质量"; + return false; + } + + h_kJkg = (h_Jmol / molarMassKgPerMol) * 0.001; + return true; + } + + private static bool TryConvertS_kJkgK_To_JmolK(double s_kJkgK, out double s_JmolK, out string error) + { + s_JmolK = double.NaN; + error = string.Empty; + + double molarMassKgPerMol = GetMolarMass(); + if (molarMassKgPerMol <= 0) + { + error = "无效的摩尔质量"; + return false; + } + + s_JmolK = s_kJkgK * 1000.0 * molarMassKgPerMol; + return true; + } + } + } +}