From e3641ebe84e1d387b8f3fb3e0e638ffc86b16840 Mon Sep 17 00:00:00 2001 From: Tyrone CT Date: Wed, 15 Apr 2026 23:26:58 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B9=B2=E5=BA=A6=E7=9A=84=E6=81=A2=E5=A4=8D?= =?UTF-8?q?=EF=BC=8C=E5=B9=B2=E5=BA=A6=E7=9A=84=E8=AE=A1=E7=AE=97=E6=81=A2?= =?UTF-8?q?=E5=A4=8D=E5=88=B04f452b5=E8=BF=99=E4=B8=AA=E5=B9=B2=E5=BA=A6?= =?UTF-8?q?=E8=AE=A1=E7=AE=97=E6=96=B9=E6=B3=95=E4=B8=8A=20=E5=B0=81?= =?UTF-8?q?=E8=A3=85=E4=BA=86=E5=B9=B2=E5=BA=A6=E7=9A=84=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EnthalpyDrynessCalculator.cs | 599 ++++++++++++++++++ CapMachine.Wpf/Services/PPCService.cs | 416 +++++------- 2 files changed, 769 insertions(+), 246 deletions(-) create mode 100644 CapMachine.Wpf/PPCalculation/EnthalpyDrynessCalculator.cs diff --git a/CapMachine.Wpf/PPCalculation/EnthalpyDrynessCalculator.cs b/CapMachine.Wpf/PPCalculation/EnthalpyDrynessCalculator.cs new file mode 100644 index 0000000..6733cf5 --- /dev/null +++ b/CapMachine.Wpf/PPCalculation/EnthalpyDrynessCalculator.cs @@ -0,0 +1,599 @@ +using System; + +namespace CapMachine.Wpf.PPCalculation +{ + /// + /// 干度计算(基于焓法)的独立封装: + /// - 先分别计算气路/液路阀前的单相质量比焓(TPRHO + THERM) + /// - 再计算吸气压力对应的饱和液/饱和气质量比焓(SATP + THERM) + /// - 最终按既定流程计算混合焓与干度,并限幅到 [0,1] + /// + /// 注意:该类仅做封装以便维护,不应改变既有干度计算过程与逻辑。 + /// 调用方需确保 REFPROP 已完成 SETPATH/SETUP 初始化(与现有 PPCService 保持一致)。 + /// + public sealed class EnthalpyDrynessCalculator + { + private readonly object _refpropLock; + + /// + /// 构造函数。 + /// + /// 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) + { + // 气体流量 kg/h = 冷媒流量 kg/h - 液冷媒流量 kg/h + double gasFlowKgPerH = input.VRVFlowKgPerH - input.LiqRefFlowKgPerH; + + // 定义气相质量焓 kJ/kg(注意:保持既有逻辑,默认 0,仅在成功计算时赋值) + double gas_hVap_kJkg = 0.0; + + // 步骤1: 计算气路阀前气相焓 h_vap (单相气相) + 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; + } + } + + // 定义液相质量焓 kJ/kg(保持既有逻辑,默认 0,仅在成功计算时赋值) + double liquid_hLiq_kJkg = 0.0; + + // 步骤2: 计算液路阀前液相焓 h_liq (单相液相) + 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; + } + } + + // 定义饱和液/饱和气质量焓 kJ/kg(保持既有逻辑,默认 0,仅在同时成功时赋值) + 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; + } + + // 干度1:mg=气体流量;ml=液体流量 + 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); + + // 干度2:mg=气体流量+润滑油流量;ml=液体流量(保持既有策略) + 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, + gas_hVap_kJkg, + liquid_hLiq_kJkg, + hSatL_kJkg, + hSatV_kJkg, + ok1, + dryness1, + hMix1, + err1, + ok2, + dryness2, + hMix2, + err2); + } + + /// + /// 获取指定组分的摩尔质量(kg/mol)。 + /// + /// 组分ID(icomp)。 + /// 摩尔质量(kg/mol)。 + /// 错误信息。 + /// 是否成功。 + private bool TryGetMolarMassKgPerMol(long componentId, out double molarMassKgPerMol, out string error) + { + molarMassKgPerMol = double.NaN; + error = string.Empty; + + try + { + double wmm = 0, Trp = 0, Tnbpt = 0, Tc = 0, Pc = 0, Dc = 0, Zc = 0, acf = 0, dip = 0, Rgas = 0; + lock (_refpropLock) + { + 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 (double.IsNaN(molarMassKgPerMol) || double.IsInfinity(molarMassKgPerMol) || molarMassKgPerMol <= 0) + { + error = "无效的摩尔质量"; + return false; + } + + return true; + } + catch (Exception ex) + { + error = $"获取组分{componentId}的摩尔质量时出错: {ex.Message}"; + return false; + } + } + + /// + /// TPRHOdll 封装:按 T(℃)、P(MPa) 计算“气相”摩尔密度 D [mol/L](kph=2)。 + /// + 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; + } + + /// + /// THERMdll 封装:按 T(℃) 与 D[mol/L] 计算气相质量焓 h_vap [kJ/kg]。 + /// + 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; + } + + /// + /// TPRHOdll 封装:按 T(℃)、P(MPa) 计算“液相”摩尔密度 D [mol/L](kph=1)。 + /// + 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; + } + + /// + /// THERMdll 封装:按 T(℃) 与 D[mol/L] 计算液相质量焓 h_liq [kJ/kg]。 + /// + 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; + } + + /// + /// SATPdll:由压力 P(MPa) 求饱和温度 Tsat[K]、饱和液/气摩尔密度 Dl/Dv [mol/L]。 + /// + 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; + } + + /// + /// THERMdll:由 T[K] 与 D[mol/L] 计算质量比焓 h[kJ/kg]。 + /// + 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; + } + + /// + /// 便捷:由压力 P(MPa) 直接得到“饱和液”质量比焓 h_liq[kJ/kg]。 + /// + 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); + } + + /// + /// 便捷:由压力 P(MPa) 直接得到“饱和气”质量比焓 h_vap[kJ/kg]。 + /// + 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); + } + + /// + /// 按图片的最终流程计算干度: + /// 1) 质量流量加权混合焓 h_mix = (h_vap*mg + h_liq*ml) / (mg + ml) + /// 2) 干度 x = (h_mix - h_l) / (h_v - h_l),并限幅到 [0,1] + /// + /// 入参单位: + /// - hVap_kJkg, hLiq_kJkg, hSatL_kJkg, hSatV_kJkg 均为 kJ/kg + /// - mGas_kg_h, mLiq_kg_h 均为 kg/h + /// + private 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; + } + + /// + /// 按图片的最终流程计算干度2: + /// 计算逻辑同干度1,但气体流量 mg = 气体流量 + 润滑油流量。 + /// + private 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; + } + } +} diff --git a/CapMachine.Wpf/Services/PPCService.cs b/CapMachine.Wpf/Services/PPCService.cs index 4ac1cde..82eca19 100644 --- a/CapMachine.Wpf/Services/PPCService.cs +++ b/CapMachine.Wpf/Services/PPCService.cs @@ -10,6 +10,7 @@ using Prism.Events; using Prism.Mvvm; using Prism.Services.Dialogs; using SixLabors.ImageSharp.ColorSpaces; +using System.Diagnostics; using System.Globalization; using System.Collections.Generic; using System.Text; @@ -35,6 +36,7 @@ namespace CapMachine.Wpf.Services public MachineRtDataService MachineRtDataService { get; } public IDialogService DialogService { get; } private readonly IDrynessCalculator _drynessCalculator; + private readonly EnthalpyDrynessCalculator _enthalpyDrynessCalculator; /// /// 标签中心 @@ -58,6 +60,7 @@ namespace CapMachine.Wpf.Services MachineRtDataService = machineRtDataService; DialogService = dialogService; _drynessCalculator = drynessCalculator; + _enthalpyDrynessCalculator = new EnthalpyDrynessCalculator(_refpropLock); TagManager = MachineRtDataService.TagManger; if (TagManager.TryGetShortTagByName("转速[rpm]", out ShortValueTag? speedShortTag)) @@ -95,8 +98,7 @@ namespace CapMachine.Wpf.Services //TxvFrTempTag = TagManager.DicTags.GetValueOrDefault("膨胀阀前温度[℃]")!; //TxvFrPressTag = TagManager.DicTags.GetValueOrDefault("膨胀阀前压力[BarA]")!; - if (TagManager.TryGetShortTagByName("膨胀阀前温度[℃]", out ShortValueTag? TxvFrTempShortTag) || - TagManager.TryGetShortTagByName("SUBCOOL出口温度[℃]", out TxvFrTempShortTag)) + if (TagManager.TryGetShortTagByName("SUBCOOL出口温度[℃]", out ShortValueTag? TxvFrTempShortTag)) { TxvFrTempTag = TxvFrTempShortTag!; } @@ -326,6 +328,70 @@ namespace CapMachine.Wpf.Services /// private bool DebugLog { get; set; } = false; + private DateTime _lastDrynessSnapshotUtc = DateTime.MinValue; + private static readonly TimeSpan _drynessSnapshotInterval = TimeSpan.FromSeconds(5); + + /// + /// 干度计算过程值输出(限频),用于现场快速定位干度为何为0/为何被限幅。 + /// + private void LogDrynessSnapshotIfNeeded( + string stage, + string error, + double dryness01, + double hMix_kJkg, + double gasInput_kJkg, + double liquidInput_kJkg, + double hSatL_kJkg, + double hSatV_kJkg, + double mGas_kg_h, + double mLube_kg_h, + double mLiq_kg_h) + { + var now = DateTime.UtcNow; + if (now - _lastDrynessSnapshotUtc < _drynessSnapshotInterval) + { + return; + } + + _lastDrynessSnapshotUtc = now; + + double denom = hSatV_kJkg - hSatL_kJkg; + double xRaw = double.NaN; + if (!double.IsNaN(hMix_kJkg) && !double.IsNaN(hSatL_kJkg) && !double.IsNaN(denom) && Math.Abs(denom) > 1e-9) + { + xRaw = (hMix_kJkg - hSatL_kJkg) / denom; + } + + double vrv = VRVTag?.PVModel?.EngValue ?? double.NaN; + double liqRef = LiqRefFlowTag?.PVModel?.EngValue ?? double.NaN; + double lube = LubeFlowTag?.PVModel?.EngValue ?? double.NaN; + double gasFlow = vrv - liqRef; + + double mg = Math.Max(0.0, mGas_kg_h) + Math.Max(0.0, mLube_kg_h); + double ml = Math.Max(0.0, mLiq_kg_h); + double mSum = mg + ml; + + var msg = + $"[DrynessSnap] Stage={stage}; Err={error}; x={dryness01:F6} ({dryness01 * 100.0:F3}%); xRaw={xRaw}; " + + $"hMix={hMix_kJkg}; hSatL={hSatL_kJkg}; hSatV={hSatV_kJkg}; denom={denom}; " + + $"GasInput={gasInput_kJkg}; LiquidInput={liquidInput_kJkg}; " + + $"mGas={mGas_kg_h}kg/h, mLube={mLube_kg_h}kg/h, mLiq={mLiq_kg_h}kg/h => mg={mg}kg/h, ml={ml}kg/h, mSum={mSum}kg/h; " + + $"VRV={vrv}kg/h(Addr={VRVTag?.PVModel?.Address}), LiqRef={liqRef}kg/h(Addr={LiqRefFlowTag?.PVModel?.Address}), Lube={lube}kg/h(Addr={LubeFlowTag?.PVModel?.Address}), GasFlow(VRV-LiqRef)={gasFlow}kg/h; " + + $"InhP={InhPressTag?.PVModel?.EngValue}BarA(Addr={InhPressTag?.PVModel?.Address}), InhT={InhTempTag?.PVModel?.EngValue}C(Addr={InhTempTag?.PVModel?.Address}), " + + $"TxvP={TxvFrPressTag?.PVModel?.EngValue}BarA(Addr={TxvFrPressTag?.PVModel?.Address}), TxvT={TxvFrTempTag?.PVModel?.EngValue}C(Addr={TxvFrTempTag?.PVModel?.Address}), " + + $"GasPreValveP={GasPreValvePressTag?.PVModel?.EngValue}BarA(Addr={GasPreValvePressTag?.PVModel?.Address}), GasPreValveT={GasPreValveTempTag?.PVModel?.EngValue}C(Addr={GasPreValveTempTag?.PVModel?.Address})"; + + Debug.WriteLine(msg); + try + { + Console.WriteLine(msg); + } + catch + { + // ignored + } + } + /// /// PLC扫描线程 /// @@ -393,99 +459,87 @@ namespace CapMachine.Wpf.Services //干度技术 - //气体流量kg/h=冷媒流量kg/h-液冷媒流量kg/h - var GasFlowKgPerH = VRVTag.PVModel.EngValue - LiqRefFlowTag.PVModel.EngValue;//气体流量kg/h + var drynessResult = _enthalpyDrynessCalculator.Calculate( + new EnthalpyDrynessCalculator.Input( + GasPreValvePressTag.PVModel.EngValue, + GasPreValveTempTag.PVModel.EngValue, + TxvFrPressTag.PVModel.EngValue, + TxvFrTempTag.PVModel.EngValue, + InhPressTag.PVModel.EngValue, + VRVTag.PVModel.EngValue, + LiqRefFlowTag.PVModel.EngValue, + LubeFlowTag.PVModel.EngValue)); - //摩尔质量(kg/mol) - var molarMassKgPerMol = GetMolarMass(); - - //定义气相质量焓 kJ/(kg·K) - var Gas_kJkgK = 0.0; - - //步骤1:计算气路阀前气相焓h vap(单相气相) - if (TryTPRHO_VaporDensity_ByTP_MPa_C(GasPreValvePressTag.PVModel.EngValue * 0.1, GasPreValveTempTag.PVModel.EngValue, out var D_molL, out var D_molLErr1)) + if (drynessResult.IsDryness1Success) { - if (TryTHERM_VaporEntropy_ByTD(GasPreValveTempTag.PVModel.EngValue, D_molL, out var s_kJkgK, out var D_molLErr2)) - { - // s_kJkgK 即为图片中的“气相质量熵 kJ/(kg·K)” - Gas_kJkgK = s_kJkgK; - } - } + DrynessTag2Value = drynessResult.Dryness1_01 * 100.0; - //定义液相质量焓 kJ/(kg·K) - var Liquid_kJkg = 0.0; - - //步骤2:计算液路阀前气相焓hlig(单相液相) TxvFrTempTag 液体阀前温度 TxvFrPressTag 液体阀前压力 - // 1) 先求 D_liq - if (TryTPRHO_LiquidDensity_ByTP_MPa_C(TxvFrPressTag.PVModel.EngValue * 0.1, TxvFrTempTag.PVModel.EngValue, out var D_liq_molL, out var D_liqErr1)) - { - // 2) 再用 THERM 求 h_liq - if (TryTHERM_LiquidEnthalpy_ByTD(TxvFrTempTag.PVModel.EngValue, D_liq_molL, out var h_liq_kJkg, out var D_liqErr2)) - { - // h_liq_kJkg 即为图片中的“液相质量焓 h_liq kJ/kg” - Liquid_kJkg = h_liq_kJkg; - } - else - { - // 处理 err2 - } + //if (drynessResult.Dryness1_01 <= 0) + //{ + // LogDrynessSnapshotIfNeeded( + // "TryComputeDrynessByEnthalpy(SuccessButZero)", + // string.Empty, + // drynessResult.Dryness1_01, + // drynessResult.HMix1_kJkg, + // drynessResult.GasEnthalpy_kJkg, + // drynessResult.LiquidEnthalpy_kJkg, + // drynessResult.SatLiquidEnthalpy_kJkg, + // drynessResult.SatVaporEnthalpy_kJkg, + // drynessResult.GasFlowKgPerH, + // 0, + // LiqRefFlowTag.PVModel.EngValue); + //} } else { - // 处理 err1 + //LogDrynessSnapshotIfNeeded( + // "TryComputeDrynessByEnthalpy(Fail)", + // drynessResult.Error1, + // double.NaN, + // double.NaN, + // drynessResult.GasEnthalpy_kJkg, + // drynessResult.LiquidEnthalpy_kJkg, + // drynessResult.SatLiquidEnthalpy_kJkg, + // drynessResult.SatVaporEnthalpy_kJkg, + // drynessResult.GasFlowKgPerH, + // 0, + // LiqRefFlowTag.PVModel.EngValue); } - //定义饱和液质量焓hl kJ/kg - var Liquid_h_liq = 0.0; - //定义饱和气质量焓hl k)/kg - var Gas_h_vap = 0.0; - - if (TryGetSaturationLiquidEnthalpy_ByP_MPa(InhPressTag.PVModel.EngValue * 0.1, out var h_liq, out var h_liqErr1) && - TryGetSaturationVaporEnthalpy_ByP_MPa(InhPressTag.PVModel.EngValue * 0.1, out var h_vap, out var h_vapErr2)) + if (drynessResult.IsDryness2Success) { - // h_liq / h_vap 即为图片右侧的两个“饱和液/饱和气 质量焓 kJ/kg” - Liquid_h_liq = h_liq; - Gas_h_vap = h_vap; - } - - // 气相焓 h_vap_kJkg(由 TPRHO 气相 + THERM) - // 液相焓 h_liq_kJkg(由 TPRHO 液相 + THERM) - // 饱和液 / 气焓 h_l / h_v(由 SATP +THERM) - //气/液质量流量 mg/ml - if (TryComputeDrynessByEnthalpy( - Gas_kJkgK, Liquid_kJkg,//气相质量焓 h vap [k/kg] 液相质量焓 h liq [kJ/kg] - GasFlowKgPerH, LiqRefFlowTag.PVModel.EngValue,//气体质量流量 mg [kg/h] 液体质量流量 ml [kg/h] - Liquid_h_liq, Gas_h_vap, //饱和液质量焓 h liq [kJ/kg] 饱和气质量焓 h vap [kJ/kg] - out var GasValue, out var hMix, out var err)) - { - DrynessTag2Value = GasValue * 100.0; - // x 为最终干度 [0..1],hMix 为混合后比焓 + DrynessTag.PVModel.EngValue = drynessResult.Dryness2_01 * 100.0; + //if (drynessResult.Dryness2_01 <= 0) + //{ + // LogDrynessSnapshotIfNeeded( + // "TryComputeDrynessByEnthalpy2(SuccessButZero)", + // string.Empty, + // drynessResult.Dryness2_01, + // drynessResult.HMix2_kJkg, + // drynessResult.GasEnthalpy_kJkg, + // drynessResult.LiquidEnthalpy_kJkg, + // drynessResult.SatLiquidEnthalpy_kJkg, + // drynessResult.SatVaporEnthalpy_kJkg, + // drynessResult.GasFlowKgPerH, + // LubeFlowTag.PVModel.EngValue, + // LiqRefFlowTag.PVModel.EngValue); + //} } else { - // 处理 err - } - - //计算干度2 - // 气相焓 h_vap_kJkg(由 TPRHO 气相 + THERM) - // 液相焓 h_liq_kJkg(由 TPRHO 液相 + THERM) - // 饱和液 / 气焓 h_l / h_v(由 SATP +THERM) - //气/液质量流量 mg/ml - //***************** 经过验证,当前方法是正确 ***************** - if (TryComputeDrynessByEnthalpy2( - Gas_kJkgK, Liquid_kJkg,//气相质量焓 h vap [k/kg] 液相质量焓 h liq [kJ/kg] - GasFlowKgPerH, LubeFlowTag.PVModel.EngValue, //气体质量流量 mg [kg/h] 润滑油流量 - LiqRefFlowTag.PVModel.EngValue,// 液体质量流量 ml [kg/h] - Liquid_h_liq, Gas_h_vap, //饱和液质量焓 h liq [kJ/kg] 饱和气质量焓 h vap [kJ/kg] - out var GasValue2, out var hMix2, out var err2)) - { - // x 为最终干度 [0..1],hMix 为混合后比焓 - DrynessTag.PVModel.EngValue = GasValue2 * 100.0; - } - else - { - // 处理 err2 + //LogDrynessSnapshotIfNeeded( + // "TryComputeDrynessByEnthalpy2(Fail)", + // drynessResult.Error2, + // double.NaN, + // double.NaN, + // drynessResult.GasEnthalpy_kJkg, + // drynessResult.LiquidEnthalpy_kJkg, + // drynessResult.SatLiquidEnthalpy_kJkg, + // drynessResult.SatVaporEnthalpy_kJkg, + // drynessResult.GasFlowKgPerH, + // LubeFlowTag.PVModel.EngValue, + // LiqRefFlowTag.PVModel.EngValue); } if (TryUpdateThermodynamicSixResults(out var thermoErr)) @@ -714,6 +768,40 @@ namespace CapMachine.Wpf.Services } + public 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; // K + double D = densityMolPerL; // mol/L + + 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_vap_kJ_per_kg = (hJmol / molarMassKgPerMol) * 0.001; + return true; + } + + /// /// /// 计算液路阀前液相密度D_liq(单相液相) 液相密度D_liq mol/L @@ -952,170 +1040,6 @@ namespace CapMachine.Wpf.Services return TryTHERM_Enthalpy_kJkg_ByT_K_D(tSatK, Dv, out h_vap_kJkg, out error); } - /// - /// 按图片的最终流程计算干度: - /// 1) 质量流量加权混合焓 h_mix = (h_vap*mg + h_liq*ml) / (mg + ml) - /// 2) 干度 x = (h_mix - h_l) / (h_v - h_l),并限幅到 [0,1] - /// - /// 入参单位: - /// - hVap_kJkg, hLiq_kJkg, hSatL_kJkg, hSatV_kJkg 均为 kJ/kg - /// - mGas_kg_h, mLiq_kg_h 均为 kg/h - /// 返回:true 表示成功,输出 x∈[0,1] 与 h_mix;false 返回 error - /// - /// 气相质量焓 h_vap [kJ/kg] - /// 液相质量焓 h_liq [kJ/kg] - /// 气体质量流量 mg [kg/h] - /// 液体质量流量 ml [kg/h] - /// 饱和液质量焓 h_l [kJ/kg] - /// 饱和气质量焓 h_v [kJ/kg] - /// 输出干度 x ∈ [0,1] - /// 输出混合后总比焓 h_mix [kJ/kg] - /// Err - /// - public bool TryComputeDrynessByEnthalpy( - double hVap_kJkg, // 气相质量焓 h_vap [kJ/kg] - double hLiq_kJkg, // 液相质量焓 h_liq [kJ/kg] - double mGas_kg_h, // 气体质量流量 mg [kg/h] - double mLiq_kg_h, // 液体质量流量 ml [kg/h] - double hSatL_kJkg, // 饱和液质量焓 h_l [kJ/kg] - double hSatV_kJkg, // 饱和气质量焓 h_v [kJ/kg] - out double dryness, // 输出干度 x ∈ [0,1] - out double hMix_kJkg, // 输出混合后总比焓 h_mix [kJ/kg] - out string error) - { - dryness = double.NaN; - hMix_kJkg = double.NaN; - error = string.Empty; - - // 1) 合法性校验 - 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; - } - // 负值处理:小于 0 视为 0(避免传感器噪声或符号错误影响) - 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; - } - - // 2) 质量流量加权混合焓(严格按图片:上、下两路相加后除以总流量) - hMix_kJkg = (hVap_kJkg * mg + hLiq_kJkg * ml) / mSum; - - // 3) 干度计算:x = (h_mix - h_l) / (h_v - h_l) - 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; - - // 4) 限幅到 [0,1] - if (double.IsNaN(x) || double.IsInfinity(x)) - { - error = "干度计算结果异常(NaN/Inf)"; - return false; - } - dryness = Math.Min(1.0, Math.Max(0.0, x)); - return true; - } - - - /// - /// 按图片的最终流程计算干度2: - /// 干度2的计算:临时用作为对比使用 - /// 1) 质量流量加权混合焓 h_mix = (h_vap*mg + h_liq*ml) / (mg + ml) - /// 2) 干度 x = (h_mix - h_l) / (h_v - h_l),并限幅到 [0,1] - /// - /// 入参单位: - /// - hVap_kJkg, hLiq_kJkg, hSatL_kJkg, hSatV_kJkg 均为 kJ/kg - /// - mGas_kg_h, mLiq_kg_h 均为 kg/h - /// 返回:true 表示成功,输出 x∈[0,1] 与 h_mix;false 返回 error - /// - /// 气相质量焓 h_vap [kJ/kg] - /// 液相质量焓 h_liq [kJ/kg] - /// 气体质量流量 mg [kg/h] - /// 液体质量流量 ml [kg/h] - /// 饱和液质量焓 h_l [kJ/kg] - /// 饱和气质量焓 h_v [kJ/kg] - /// 输出干度 x ∈ [0,1] - /// 输出混合后总比焓 h_mix [kJ/kg] - /// Err - /// - public bool TryComputeDrynessByEnthalpy2( - double hVap_kJkg, // 气相质量焓 h_vap [kJ/kg] - double hLiq_kJkg, // 液相质量焓 h_liq [kJ/kg] - double mGas_kg_h, // 气体质量流量 mg [kg/h] - double lubeFlow_kg_h, // 润滑油流量 mg [kg/h] - double mLiq_kg_h, // 液体质量流量 ml [kg/h] - double hSatL_kJkg, // 饱和液质量焓 h_l [kJ/kg] - double hSatV_kJkg, // 饱和气质量焓 h_v [kJ/kg] - out double dryness, // 输出干度 x ∈ [0,1] - out double hMix_kJkg, // 输出混合后总比焓 h_mix [kJ/kg] - out string error) - { - dryness = double.NaN; - hMix_kJkg = double.NaN; - error = string.Empty; - - // 1) 合法性校验 - 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; - } - // 负值处理:小于 0 视为 0(避免传感器噪声或符号错误影响) - //double mg1 = Math.Max(0.0, mGas_kg_h); - 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; - } - - // 2) 质量流量加权混合焓(严格按图片:上、下两路相加后除以总流量) - hMix_kJkg = (hVap_kJkg * mg + hLiq_kJkg * ml) / mSum; - - // 3) 干度计算:x = (h_mix - h_l) / (h_v - h_l) - 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; - - // 4) 限幅到 [0,1] - if (double.IsNaN(x) || double.IsInfinity(x)) - { - error = "干度计算结果异常(NaN/Inf)"; - return false; - } - dryness = Math.Min(1.0, Math.Max(0.0, x)); - return true; - } -