Files
CapMachine/CapMachine.Wpf/PPCalculation/EnthalpyDrynessCalculator.cs
2026-04-15 23:26:58 +08:00

600 lines
21 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
namespace CapMachine.Wpf.PPCalculation
{
/// <summary>
/// 干度计算(基于焓法)的独立封装:
/// - 先分别计算气路/液路阀前的单相质量比焓TPRHO + THERM
/// - 再计算吸气压力对应的饱和液/饱和气质量比焓SATP + THERM
/// - 最终按既定流程计算混合焓与干度,并限幅到 [0,1]
///
/// 注意:该类仅做封装以便维护,不应改变既有干度计算过程与逻辑。
/// 调用方需确保 REFPROP 已完成 SETPATH/SETUP 初始化(与现有 PPCService 保持一致)。
/// </summary>
public sealed class EnthalpyDrynessCalculator
{
private readonly object _refpropLock;
/// <summary>
/// 构造函数。
/// </summary>
/// <param name="refpropLock">REFPROP 全局互斥锁对象(必须与系统其它 REFPROP 调用共用,以避免并发竞态)。</param>
public EnthalpyDrynessCalculator(object refpropLock)
{
_refpropLock = refpropLock ?? throw new ArgumentNullException(nameof(refpropLock));
}
/// <summary>
/// 干度计算输入模型(以 Tag 读数为准)。
/// </summary>
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; }
}
/// <summary>
/// 干度计算输出模型。
/// </summary>
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; }
}
/// <summary>
/// 按既有流程计算干度两套干度1/干度2
/// </summary>
/// <param name="input">输入数据。</param>
/// <returns>计算结果。</returns>
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;
}
// 干度1mg=气体流量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);
// 干度2mg=气体流量+润滑油流量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);
}
/// <summary>
/// 获取指定组分的摩尔质量kg/mol
/// </summary>
/// <param name="componentId">组分IDicomp。</param>
/// <param name="molarMassKgPerMol">摩尔质量kg/mol。</param>
/// <param name="error">错误信息。</param>
/// <returns>是否成功。</returns>
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;
}
}
/// <summary>
/// TPRHOdll 封装:按 T(℃)、P(MPa) 计算“气相”摩尔密度 D [mol/L]kph=2
/// </summary>
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;
}
/// <summary>
/// THERMdll 封装:按 T(℃) 与 D[mol/L] 计算气相质量焓 h_vap [kJ/kg]。
/// </summary>
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;
}
/// <summary>
/// TPRHOdll 封装:按 T(℃)、P(MPa) 计算“液相”摩尔密度 D [mol/L]kph=1
/// </summary>
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;
}
/// <summary>
/// THERMdll 封装:按 T(℃) 与 D[mol/L] 计算液相质量焓 h_liq [kJ/kg]。
/// </summary>
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;
}
/// <summary>
/// SATPdll由压力 P(MPa) 求饱和温度 Tsat[K]、饱和液/气摩尔密度 Dl/Dv [mol/L]。
/// </summary>
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;
}
/// <summary>
/// THERMdll由 T[K] 与 D[mol/L] 计算质量比焓 h[kJ/kg]。
/// </summary>
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;
}
/// <summary>
/// 便捷:由压力 P(MPa) 直接得到“饱和液”质量比焓 h_liq[kJ/kg]。
/// </summary>
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);
}
/// <summary>
/// 便捷:由压力 P(MPa) 直接得到“饱和气”质量比焓 h_vap[kJ/kg]。
/// </summary>
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);
}
/// <summary>
/// 按图片的最终流程计算干度:
/// 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
/// </summary>
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;
}
/// <summary>
/// 按图片的最终流程计算干度2
/// 计算逻辑同干度1但气体流量 mg = 气体流量 + 润滑油流量。
/// </summary>
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;
}
}
}