Files
CapMachine/CapMachine.Wpf/PPCalculation/EnthalpyDrynessCalculator.cs
Tyrone CT 47834ea4dc 物性更改2
一些已知的更改
2026-05-07 22:11:27 +08:00

645 lines
22 KiB
C#
Raw Permalink 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 CapMachine.Core;
using System;
namespace CapMachine.Wpf.PPCalculation
{
/// <summary>
/// 干度(品质)计算器:按“焓加权混合 + 饱和焓归一化”的流程计算 Dryness1/Dryness2。
/// </summary>
public sealed class EnthalpyDrynessCalculator
{
private readonly object _refpropLock;
private static volatile bool _rpInitialized;
/// <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)
{
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);
}
/// <summary>
/// REFPROP 初始化(幂等)。
/// </summary>
/// <param name="error">失败原因。</param>
/// <returns>是否初始化成功。</returns>
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 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;
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 (molarMassKgPerMol <= 0)
{
error = "无效的摩尔质量";
return false;
}
return true;
}
catch (Exception ex)
{
error = $"读取摩尔质量异常: {ex.Message}";
return false;
}
}
}
}