using CapMachine.Core; using System; namespace CapMachine.Wpf.PPCalculation { /// /// 干度(品质)计算器:按“焓加权混合 + 饱和焓归一化”的流程计算 Dryness1/Dryness2。 /// public sealed class EnthalpyDrynessCalculator { private readonly object _refpropLock; private static volatile bool _rpInitialized; /// /// 构造函数。 /// /// REFPROP 全局互斥锁对象(必须与系统其它 REFPROP 调用共用,以避免并发竞态)。 public EnthalpyDrynessCalculator(object refpropLock) { _refpropLock = refpropLock ?? throw new ArgumentNullException(nameof(refpropLock)); } /// /// 干度计算输入模型(以 Tag 读数为准)。 /// public readonly struct Input { public Input( double gasPreValvePressBarA, double gasPreValveTempC, double txvFrPressBarA, double txvFrTempC, double inhPressBarA, double vrvFlowKgPerH, double liqRefFlowKgPerH, double lubeFlowKgPerH) { GasPreValvePressBarA = gasPreValvePressBarA; GasPreValveTempC = gasPreValveTempC; TxvFrPressBarA = txvFrPressBarA; TxvFrTempC = txvFrTempC; InhPressBarA = inhPressBarA; VRVFlowKgPerH = vrvFlowKgPerH; LiqRefFlowKgPerH = liqRefFlowKgPerH; LubeFlowKgPerH = lubeFlowKgPerH; } public double GasPreValvePressBarA { get; } public double GasPreValveTempC { get; } public double TxvFrPressBarA { get; } public double TxvFrTempC { get; } public double InhPressBarA { get; } public double VRVFlowKgPerH { get; } public double LiqRefFlowKgPerH { get; } public double LubeFlowKgPerH { get; } } /// /// 干度计算输出模型。 /// public readonly struct Result { public Result( double gasFlowKgPerH, double gasEnthalpy_kJkg, double liquidEnthalpy_kJkg, double satLiquidEnthalpy_kJkg, double satVaporEnthalpy_kJkg, bool isDryness1Success, double dryness1_01, double hMix1_kJkg, string error1, bool isDryness2Success, double dryness2_01, double hMix2_kJkg, string error2) { GasFlowKgPerH = gasFlowKgPerH; GasEnthalpy_kJkg = gasEnthalpy_kJkg; LiquidEnthalpy_kJkg = liquidEnthalpy_kJkg; SatLiquidEnthalpy_kJkg = satLiquidEnthalpy_kJkg; SatVaporEnthalpy_kJkg = satVaporEnthalpy_kJkg; IsDryness1Success = isDryness1Success; Dryness1_01 = dryness1_01; HMix1_kJkg = hMix1_kJkg; Error1 = error1 ?? string.Empty; IsDryness2Success = isDryness2Success; Dryness2_01 = dryness2_01; HMix2_kJkg = hMix2_kJkg; Error2 = error2 ?? string.Empty; } public double GasFlowKgPerH { get; } public double GasEnthalpy_kJkg { get; } public double LiquidEnthalpy_kJkg { get; } public double SatLiquidEnthalpy_kJkg { get; } public double SatVaporEnthalpy_kJkg { get; } public bool IsDryness1Success { get; } public double Dryness1_01 { get; } public double HMix1_kJkg { get; } public string Error1 { get; } public bool IsDryness2Success { get; } public double Dryness2_01 { get; } public double HMix2_kJkg { get; } public string Error2 { get; } } /// /// 计算干度 1/2。 /// /// 输入数据。 /// 计算结果(包含两路干度及中间量)。 public Result Calculate(Input input) { if (!EnsureRefpropInitialized(out _)) { return new Result( gasFlowKgPerH: double.NaN, gasEnthalpy_kJkg: double.NaN, liquidEnthalpy_kJkg: double.NaN, satLiquidEnthalpy_kJkg: double.NaN, satVaporEnthalpy_kJkg: double.NaN, isDryness1Success: false, dryness1_01: double.NaN, hMix1_kJkg: double.NaN, error1: "REFPROP 未初始化", isDryness2Success: false, dryness2_01: double.NaN, hMix2_kJkg: double.NaN, error2: "REFPROP 未初始化"); } double gasFlowKgPerH = input.VRVFlowKgPerH - input.LiqRefFlowKgPerH; double gas_hVap_kJkg = 0.0; if (TryTPRHO_VaporDensity_ByTP_MPa_C(input.GasPreValvePressBarA * 0.1, input.GasPreValveTempC, out var dVap_molL, out _)) { if (TryTHERM_VaporEnthalpy_ByTD(input.GasPreValveTempC, dVap_molL, out var hVap_kJkg, out _)) { gas_hVap_kJkg = hVap_kJkg; } } double liquid_hLiq_kJkg = 0.0; if (TryTPRHO_LiquidDensity_ByTP_MPa_C(input.TxvFrPressBarA * 0.1, input.TxvFrTempC, out var dLiq_molL, out _)) { if (TryTHERM_LiquidEnthalpy_ByTD(input.TxvFrTempC, dLiq_molL, out var hLiq_kJkg, out _)) { liquid_hLiq_kJkg = hLiq_kJkg; } } double hSatL_kJkg = 0.0; double hSatV_kJkg = 0.0; if (TryGetSaturationLiquidEnthalpy_ByP_MPa(input.InhPressBarA * 0.1, out var satL, out _) && TryGetSaturationVaporEnthalpy_ByP_MPa(input.InhPressBarA * 0.1, out var satV, out _)) { hSatL_kJkg = satL; hSatV_kJkg = satV; } bool ok1 = TryComputeDrynessByEnthalpy( gas_hVap_kJkg, liquid_hLiq_kJkg, gasFlowKgPerH, input.LiqRefFlowKgPerH, hSatL_kJkg, hSatV_kJkg, out var dryness1, out var hMix1, out var err1); bool ok2 = TryComputeDrynessByEnthalpy2( gas_hVap_kJkg, liquid_hLiq_kJkg, gasFlowKgPerH, input.LubeFlowKgPerH, input.LiqRefFlowKgPerH, hSatL_kJkg, hSatV_kJkg, out var dryness2, out var hMix2, out var err2); return new Result( gasFlowKgPerH: gasFlowKgPerH, gasEnthalpy_kJkg: gas_hVap_kJkg, liquidEnthalpy_kJkg: liquid_hLiq_kJkg, satLiquidEnthalpy_kJkg: hSatL_kJkg, satVaporEnthalpy_kJkg: hSatV_kJkg, isDryness1Success: ok1, dryness1_01: dryness1, hMix1_kJkg: hMix1, error1: err1, isDryness2Success: ok2, dryness2_01: dryness2, hMix2_kJkg: hMix2, error2: err2); } /// /// REFPROP 初始化(幂等)。 /// /// 失败原因。 /// 是否初始化成功。 private 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; } } private 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; } private 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; } private bool TryTHERM_VaporEnthalpy_ByTD(double temperatureC, double densityMolPerL, out double h_vap_kJ_per_kg, out string error) { h_vap_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, e = 0, hJmol = 0, sJmolK = 0, cv = 0, cp = 0, w = 0, hjt = 0; if (!TryGetMolarMassKgPerMol(1, out var molarMassKgPerMol, out 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_vap_kJ_per_kg = (hJmol / molarMassKgPerMol) * 0.001; return true; } private 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, e = 0, hJmol = 0, sJmolK = 0, cv = 0, cp = 0, w = 0, hjt = 0; if (!TryGetMolarMassKgPerMol(1, out var molarMassKgPerMol, out 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; } private 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, 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; } private 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; if (!TryGetMolarMassKgPerMol(1, out var molarMassKgPerMol, out 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; } private 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); } private 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); } private static bool TryComputeDrynessByEnthalpy( double hVap_kJkg, double hLiq_kJkg, double mGas_kg_h, double mLiq_kg_h, double hSatL_kJkg, double hSatV_kJkg, out double dryness, out double hMix_kJkg, out string error) { dryness = double.NaN; hMix_kJkg = double.NaN; error = string.Empty; if (double.IsNaN(hVap_kJkg) || double.IsNaN(hLiq_kJkg) || double.IsNaN(hSatL_kJkg) || double.IsNaN(hSatV_kJkg)) { error = "输入焓值存在 NaN"; return false; } if (double.IsNaN(mGas_kg_h) || double.IsNaN(mLiq_kg_h)) { error = "输入质量流量存在 NaN"; return false; } double mg = Math.Max(0.0, mGas_kg_h); double ml = Math.Max(0.0, mLiq_kg_h); double mSum = mg + ml; if (mSum <= 0) { error = "气液质量流量之和为 0,无法进行加权混合焓计算"; return false; } hMix_kJkg = (hVap_kJkg * mg + hLiq_kJkg * ml) / mSum; double denom = (hSatV_kJkg - hSatL_kJkg); const double eps = 1e-9; if (Math.Abs(denom) < eps) { error = "饱和气/液焓差过小,无法计算干度(可能接近临界点或输入异常)"; return false; } double x = (hMix_kJkg - hSatL_kJkg) / denom; if (double.IsNaN(x) || double.IsInfinity(x)) { error = "干度计算结果异常(NaN/Inf)"; return false; } dryness = Math.Min(1.0, Math.Max(0.0, x)); return true; } private static bool TryComputeDrynessByEnthalpy2( double hVap_kJkg, double hLiq_kJkg, double mGas_kg_h, double lubeFlow_kg_h, double mLiq_kg_h, double hSatL_kJkg, double hSatV_kJkg, out double dryness, out double hMix_kJkg, out string error) { dryness = double.NaN; hMix_kJkg = double.NaN; error = string.Empty; if (double.IsNaN(hVap_kJkg) || double.IsNaN(hLiq_kJkg) || double.IsNaN(hSatL_kJkg) || double.IsNaN(hSatV_kJkg)) { error = "输入焓值存在 NaN"; return false; } if (double.IsNaN(mGas_kg_h) || double.IsNaN(mLiq_kg_h)) { error = "输入质量流量存在 NaN"; return false; } double mg = Math.Max(0.0, mGas_kg_h) + Math.Max(0.0, lubeFlow_kg_h); double ml = Math.Max(0.0, mLiq_kg_h); double mSum = mg + ml; if (mSum <= 0) { error = "气液质量流量之和为 0,无法进行加权混合焓计算"; return false; } hMix_kJkg = (hVap_kJkg * mg + hLiq_kJkg * ml) / mSum; double denom = (hSatV_kJkg - hSatL_kJkg); const double eps = 1e-9; if (Math.Abs(denom) < eps) { error = "饱和气/液焓差过小,无法计算干度(可能接近临界点或输入异常)"; return false; } double x = (hMix_kJkg - hSatL_kJkg) / denom; if (double.IsNaN(x) || double.IsInfinity(x)) { error = "干度计算结果异常(NaN/Inf)"; return false; } dryness = Math.Min(1.0, Math.Max(0.0, x)); return true; } private static bool TryGetMolarMassKgPerMol(long componentId, out double molarMassKgPerMol, out string error) { molarMassKgPerMol = double.NaN; error = string.Empty; try { 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; IRefProp64.INFOdll(ref componentId, ref wmm, ref Trp, ref Tnbpt, ref Tc, ref Pc, ref Dc, ref Zc, ref acf, ref dip, ref Rgas); molarMassKgPerMol = wmm * 0.001; if (molarMassKgPerMol <= 0) { error = "无效的摩尔质量"; return false; } return true; } catch (Exception ex) { error = $"读取摩尔质量异常: {ex.Message}"; return false; } } } }