干度计算V0
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
using CapMachine.Core;
|
||||
using CapMachine.Core;
|
||||
using CapMachine.Shared.Controls;
|
||||
using CapMachine.Wpf.Models.PPCalc;
|
||||
using CapMachine.Wpf.Models.Tag;
|
||||
@@ -98,10 +98,16 @@ namespace CapMachine.Wpf.Services
|
||||
Subcool = SubcoolShortTag!;
|
||||
}
|
||||
|
||||
// 干度标签(如不存在则仅跳过写入,不抛异常)
|
||||
if (TagManager.TryGetShortTagByName("干度[-]", out ShortValueTag? DrynessShortTag))
|
||||
{
|
||||
DrynessTag = DrynessShortTag!;
|
||||
}
|
||||
|
||||
SuperHeatCoolConfig.FluidsPath = ConfigHelper.GetValue("FluidsPath");
|
||||
SuperHeatCoolConfig.Cryogen = ConfigHelper.GetValue("Cryogen");
|
||||
|
||||
//RtScanDeviceStart();
|
||||
RtScanDeviceStart();
|
||||
}
|
||||
|
||||
|
||||
@@ -149,6 +155,11 @@ namespace CapMachine.Wpf.Services
|
||||
/// </summary>
|
||||
public ShortValueTag Subcool { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 干度(无量纲 [-])
|
||||
/// </summary>
|
||||
public ShortValueTag DrynessTag { get; set; }
|
||||
|
||||
|
||||
///// <summary>
|
||||
///// 过热度
|
||||
@@ -227,7 +238,8 @@ namespace CapMachine.Wpf.Services
|
||||
}
|
||||
else
|
||||
{
|
||||
hfld = "R134A.FLD";
|
||||
// 与其它实现保持一致:非 R134a 时使用 R1234YF
|
||||
hfld = "R1234YF.FLD";
|
||||
}
|
||||
//string hfld = "R134A.FLD";
|
||||
//string hfld = Convert.ToString(comboBox1.Text);//从控件获取数据
|
||||
@@ -275,6 +287,21 @@ namespace CapMachine.Wpf.Services
|
||||
//textBox6.Text = String.Format("{0:n4}", Convert.ToDouble(textBox4.Text) - (te1 - 273.15));//textBox4 Evap.膨胀阀前温度(℃)
|
||||
else
|
||||
Subcool.PVModel.EngValue = 0;
|
||||
|
||||
// 干度计算与写入:保持与当前标签单位一致(MPa/℃ 转换在内部完成)
|
||||
// 优先返回质量基干度,其次摩尔基;若均无效则置 0,避免出现 NaN 传入 UI
|
||||
try
|
||||
{
|
||||
double dryness = GetDrynessByTP(InhPressTag.PVModel.EngValue, InhTempTag.PVModel.EngValue, true);
|
||||
if (DrynessTag != null)
|
||||
{
|
||||
DrynessTag.PVModel.EngValue = double.IsNaN(dryness) ? 0.0 : dryness;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// 干度计算异常时不影响主循环,必要时可接入日志系统
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -460,6 +487,338 @@ namespace CapMachine.Wpf.Services
|
||||
//Console.WriteLine($"时间:{DateTime.Now.ToString()}摄氏干球温度 单位℃:{Cd} 相对湿度 单位%:{CRh}-零调差压 单位pa:{PKK}-大气压 单位Kpa:{BPRES}-喷嘴差压 单位Pa:{CDP}-喷嘴修正系数:{prmAirFlowFactor_i} 80通道风量结果:{Result}");
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region 干度计算相关
|
||||
|
||||
/*
|
||||
* 干度计算——实现说明(阅读指引)
|
||||
*
|
||||
* 一、目标
|
||||
* 在实时测试场景下,根据测得的温度(℃)与压力(可为 MPa 或 BarA),
|
||||
* 通过 REFPROP 库计算工质当前状态的“干度”(两相区内蒸汽相所占比例,0~1)。
|
||||
*
|
||||
* 二、单位约定
|
||||
* - REFPROP 要求:T[K],P[kPa]
|
||||
* - 本实现对外接口:
|
||||
* 1) CalcDrynessByTP_MPaC / GetDrynessByTP 接受 MPa、℃
|
||||
* 2) CalcDrynessByTP_BarA_C / GetDrynessByTP_BarA 接受 BarA、℃,内部转换为 MPa
|
||||
* - 注意与现有“过热度/过冷度”实现的单位保持一致,避免歧义
|
||||
*
|
||||
* 三、核心流程
|
||||
* STEP 0:初始化 REFPROP(路径/工质/参考态),仅在必要时执行(幂等)
|
||||
* STEP 1:使用 TPFLSHdll 做 T-P 闪蒸,获取:密度、焓 h、摩尔基干度 q 等
|
||||
* STEP 2:按 q 判断相态:
|
||||
* - 0<=q<=1:两相区,调用 QMASSdll 将 q(摩尔基)转换为质量基干度
|
||||
* - q>1:过热蒸汽;q<0:过冷液体;q==999:超临界(干度无定义)
|
||||
* STEP 3:计算同压饱和液/气焓(SATPdll -> THERMdll)得到 Hl/Hv
|
||||
* STEP 4:若不在两相区,则按 h=(1-x)*Hl + x*Hv 反推干度 x,并限幅到 [0,1]
|
||||
*
|
||||
* 四、并发与鲁棒性
|
||||
* - _refpropLock:所有 REFPROP 调用均串行化,避免非托管库内部状态竞态
|
||||
* - 异常和错误码:捕获异常并透传可读错误文本到 DrynessCalcResult.Error
|
||||
* - 临界点附近:当 Hv≈Hl 时进行防护,避免数值不稳定
|
||||
*
|
||||
* 五、典型用法
|
||||
* - 仅取数值:GetDrynessByTP / GetDrynessByTP_BarA(返回 [0,1] 或 NaN)
|
||||
* - 取完整诊断信息:CalcDrynessByTP_MPaC / CalcDrynessByTP_BarA_C
|
||||
* - 基于当前标签:CalcDrynessAtInlet(使用现有“吸气压力/吸气温度”标签)
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// 干度计算返回结果模型。为实时轮询而设计,包含计算状态与关键物性量。
|
||||
/// </summary>
|
||||
public struct DrynessCalcResult
|
||||
{
|
||||
public double QMolar; // 摩尔基干度 [0,1],若无效则为 NaN
|
||||
public double QMass; // 质量基干度 [0,1],若无效则为 NaN
|
||||
public DrynessState State;
|
||||
public double H; // 当前态焓 J/mol
|
||||
public double Hl; // 饱和液相焓 J/mol(同压)
|
||||
public double Hv; // 饱和气相焓 J/mol(同压)
|
||||
public string Error; // 错误或提示信息
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 干度计算的工况判定
|
||||
/// </summary>
|
||||
public enum DrynessState
|
||||
{
|
||||
Unknown = 0,
|
||||
TwoPhase = 1,
|
||||
Superheated = 2,
|
||||
Subcooled = 3,
|
||||
Supercritical = 4
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// REFPROP 调用的并发锁。由于 REFPROP 为非托管库且内部通常为全局状态,
|
||||
/// 在多线程/高频轮询调用时建议串行化访问,避免竞态与资源冲突。
|
||||
/// </summary>
|
||||
private readonly object _refpropLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// REFPROP 初始化标记,避免重复 SETUP 开销
|
||||
/// </summary>
|
||||
private bool _rpInitialized = false;
|
||||
|
||||
/// <summary>
|
||||
/// 已初始化的流体文件名(例如 R134A.FLD)
|
||||
/// </summary>
|
||||
private string _rpFluidFile = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 确保 REFPROP 完成初始化:设置流体路径、装载工质并建立参考态。
|
||||
/// 该方法具备幂等性,多次调用只在必要时重新初始化。
|
||||
/// </summary>
|
||||
private bool EnsureRefpropInitialized(out string error)
|
||||
{
|
||||
error = string.Empty;
|
||||
|
||||
string hpath = string.IsNullOrWhiteSpace(SuperHeatCoolConfig?.FluidsPath)
|
||||
? @".\PPCalculation\REFPROP\FLUIDS"
|
||||
: SuperHeatCoolConfig.FluidsPath;
|
||||
|
||||
// 与 REFPROPSum 中逻辑保持一致:R134a -> R134A.FLD,其它 -> R1234YF.FLD
|
||||
string hfldCore = (SuperHeatCoolConfig?.Cryogen ?? "R134a").Equals("R134a", StringComparison.OrdinalIgnoreCase)
|
||||
? "R134A.FLD"
|
||||
: "R1234YF.FLD";
|
||||
|
||||
if (_rpInitialized && string.Equals(_rpFluidFile, hfldCore, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
lock (_refpropLock)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = $"REFPROP 初始化异常: {ex.Message}";
|
||||
_rpInitialized = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算干度(T-P 闪蒸),输入为压力(MPa)与温度(℃)。
|
||||
/// 调用逻辑:
|
||||
/// 1) 通过 TPFLSHdll 获取整体态性质与摩尔基干度 q;
|
||||
/// 2) 若 q∈[0,1],使用 QMASSdll 转换为质量基干度;
|
||||
/// 3) 若 q 不在 [0,1](过冷/过热/超临界等),则以同压饱和液/气焓为基准,按 h=(1-x)*hl+x*hv 反推 x(并限幅到 [0,1])。
|
||||
/// </summary>
|
||||
public DrynessCalcResult CalcDrynessByTP_MPaC(double pressureMPa, double temperatureC)
|
||||
{
|
||||
var result = new DrynessCalcResult
|
||||
{
|
||||
QMolar = double.NaN,
|
||||
QMass = double.NaN,
|
||||
State = DrynessState.Unknown,
|
||||
H = double.NaN,
|
||||
Hl = double.NaN,
|
||||
Hv = double.NaN,
|
||||
Error = string.Empty
|
||||
};
|
||||
|
||||
// STEP 0:初始化 REFPROP(路径/工质/参考态),幂等
|
||||
if (!EnsureRefpropInitialized(out string initErr))
|
||||
{
|
||||
result.Error = initErr;
|
||||
return result;
|
||||
}
|
||||
|
||||
// 纯工质摩尔分数:x[0]=1
|
||||
double[] x = new double[20];
|
||||
x[0] = 1.0;
|
||||
|
||||
// 外部温度(℃) -> K;压力(MPa) -> kPa
|
||||
double tK = temperatureC + 273.15;
|
||||
double pKPa = pressureMPa * 1000.0; // MPa -> kPa
|
||||
|
||||
double d = 0, Dl = 0, Dv = 0, q = 0, ee = 0, h = 0, ss = 0, cp = 0, cv = 0, w = 0;
|
||||
double[] xliq = new double[20];
|
||||
double[] xvap = new double[20];
|
||||
string herr = new string(' ', 255);
|
||||
long ierr = 0, ln = 255;
|
||||
|
||||
double tSat = 0, DlSat = 0, DvSat = 0;
|
||||
string herrSat = new string(' ', 255);
|
||||
long ierrSat = 0, kph = 1, herrLen = 255;
|
||||
|
||||
double hl = double.NaN, hv = double.NaN, el = 0, sl = 0, cvl = 0, cpl = 0, wl = 0, hjt = 0;
|
||||
|
||||
lock (_refpropLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
// STEP 1:T-P 闪蒸(核心调用)——获取当前态的性质与摩尔基干度 q
|
||||
IRefProp64.TPFLSHdll(ref tK, 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 ln);
|
||||
result.H = h;
|
||||
|
||||
if (ierr != 0)
|
||||
{
|
||||
result.Error = $"TPFLSH 错误: {herr.Trim()} (ierr={ierr})";
|
||||
}
|
||||
|
||||
// STEP 2:依据 q 判断相态,并在两相区内获取质量基干度
|
||||
if (q >= 0 && q <= 1)
|
||||
{
|
||||
result.State = DrynessState.TwoPhase;
|
||||
result.QMolar = q;
|
||||
|
||||
double qkg = double.NaN, wliq = 0, wvap = 0;
|
||||
double[] xlkg = new double[20];
|
||||
double[] xvkg = new double[20];
|
||||
long ierrQ = 0; string herrQ = new string(' ', 255); long lnQ = 255;
|
||||
IRefProp64.QMASSdll(ref q, xliq, xvap, ref qkg, xlkg, xvkg, ref wliq, ref wvap, ref ierrQ, ref herrQ, ref lnQ);
|
||||
if (ierrQ == 0) result.QMass = qkg; else result.Error = $"QMASS 错误: {herrQ.Trim()} (ierr={ierrQ})";
|
||||
}
|
||||
else if (q == 999)
|
||||
{
|
||||
result.State = DrynessState.Supercritical;
|
||||
result.Error = "超临界状态,干度无定义";
|
||||
}
|
||||
else if (q > 1)
|
||||
{
|
||||
result.State = DrynessState.Superheated;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.State = DrynessState.Subcooled;
|
||||
}
|
||||
|
||||
// STEP 3:计算同压饱和点,并求取饱和液/气焓 Hl/Hv
|
||||
double pForSat = pKPa;
|
||||
IRefProp64.SATPdll(ref pForSat, x, ref kph, ref tSat, ref DlSat, ref DvSat, xliq, xvap, ref ierrSat, ref herrSat, ref herrLen);
|
||||
if (ierrSat == 0)
|
||||
{
|
||||
// 饱和液焓
|
||||
double tSatLocal = tSat; double DlLocal = DlSat; double pOut = 0;
|
||||
IRefProp64.THERMdll(ref tSatLocal, ref DlLocal, xliq, ref pOut, ref el, ref hl, ref sl, ref cvl, ref cpl, ref wl, ref hjt);
|
||||
// 饱和气焓
|
||||
tSatLocal = tSat; double DvLocal = DvSat; pOut = 0; double ev = 0, hvLocal = 0, sv = 0, cvv = 0, cpv = 0, wv = 0, hjtv = 0;
|
||||
IRefProp64.THERMdll(ref tSatLocal, ref DvLocal, xvap, ref pOut, ref ev, ref hvLocal, ref sv, ref cvv, ref cpv, ref wv, ref hjtv);
|
||||
|
||||
result.Hl = hl;
|
||||
result.Hv = hvLocal;
|
||||
|
||||
// STEP 4:若不在两相区,依据焓差回推干度并限幅
|
||||
if (double.IsNaN(result.QMolar) || result.State != DrynessState.TwoPhase)
|
||||
{
|
||||
double denom = (hvLocal - hl);
|
||||
if (Math.Abs(denom) > 1e-6)
|
||||
{
|
||||
double qh = (h - hl) / denom;
|
||||
qh = Math.Min(1.0, Math.Max(0.0, qh));
|
||||
if (double.IsNaN(result.QMolar)) result.QMolar = qh;
|
||||
if (double.IsNaN(result.QMass)) result.QMass = qh; // 纯工质近似
|
||||
if (string.IsNullOrWhiteSpace(result.Error)) result.Error = "非两相态,基于焓差的干度估算(已限幅)";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(result.Error)) result.Error = "临界点附近或数据异常导致饱和焓差接近 0,无法通过焓差估算干度";
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(result.Error)) result.Error = $"SATP 失败: {herrSat.Trim()} (ierr={ierrSat})";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.Error = $"干度计算异常: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 便捷接口:仅返回干度数值 [0,1]。默认返回质量基干度;若质量基无效,则回退摩尔基;
|
||||
/// 若均无效则返回 NaN,并可通过 <see cref="CalcDrynessByTP_MPaC"/> 获取详细错误原因。
|
||||
/// </summary>
|
||||
/// <param name="pressureMPa">压力(MPa)</param>
|
||||
/// <param name="temperatureC">温度(℃)</param>
|
||||
/// <param name="preferMassBasis">是否优先返回质量基干度(默认 true)</param>
|
||||
public double GetDrynessByTP(double pressureMPa, double temperatureC, bool preferMassBasis = true)
|
||||
{
|
||||
var r = CalcDrynessByTP_MPaC(pressureMPa, temperatureC);
|
||||
if (preferMassBasis)
|
||||
{
|
||||
if (!double.IsNaN(r.QMass)) return r.QMass;
|
||||
if (!double.IsNaN(r.QMolar)) return r.QMolar;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!double.IsNaN(r.QMolar)) return r.QMolar;
|
||||
if (!double.IsNaN(r.QMass)) return r.QMass;
|
||||
}
|
||||
return double.NaN;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 便捷接口(BarA 压力输入):仅返回干度数值 [0,1]。
|
||||
/// 内部将 BarA 转为 MPa(1 bar = 0.1 MPa)。
|
||||
/// </summary>
|
||||
public double GetDrynessByTP_BarA(double pressureBarA, double temperatureC, bool preferMassBasis = true)
|
||||
{
|
||||
return GetDrynessByTP(pressureBarA / 10.0, temperatureC, preferMassBasis);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 详细结果版本(BarA 压力输入):返回包含状态与焓信息的结构体。
|
||||
/// 内部将 BarA 转为 MPa(1 bar = 0.1 MPa)。
|
||||
/// </summary>
|
||||
public DrynessCalcResult CalcDrynessByTP_BarA_C(double pressureBarA, double temperatureC)
|
||||
{
|
||||
return CalcDrynessByTP_MPaC(pressureBarA / 10.0, temperatureC);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于当前标签(吸气压力[BarA]/吸气温度[℃])的干度计算便捷方法。
|
||||
/// 注意:为保持与现有过热度/过冷度计算一致,此处将压力值按 MPa 处理再乘以 1000 转 kPa。
|
||||
/// 若现场标签单位确认为 BarA,请在调用方转换成 MPa 后再调用上层 CalcDrynessByTP_MPaC,以避免单位歧义。
|
||||
/// </summary>
|
||||
public DrynessCalcResult CalcDrynessAtInlet()
|
||||
{
|
||||
double pressureMPa = InhPressTag?.PVModel?.EngValue ?? 0.0; // 与现有实现保持一致
|
||||
double temperatureC = InhTempTag?.PVModel?.EngValue ?? 0.0;
|
||||
return CalcDrynessByTP_MPaC(pressureMPa, temperatureC);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user