410 lines
17 KiB
C#
410 lines
17 KiB
C#
using CapMachine.Core;
|
||
using CapMachine.Wpf.PPCalculation;
|
||
using System;
|
||
|
||
namespace CapMachine.Wpf.Services
|
||
{
|
||
/// <summary>
|
||
/// 过热度 / 过冷度联合计算的输入数据。
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// 该输入对象只承载计算所需的原始测点数据,不负责任何单位换算或有效性判断。
|
||
/// 计算时各字段的单位约定如下:
|
||
/// - <see cref="SuctionPressureBarA"/>:吸气压力,单位 BarA(绝压)
|
||
/// - <see cref="SuctionTemperatureC"/>:吸气温度,单位 ℃
|
||
/// - <see cref="LiquidPressureBarA"/>:膨胀阀前液路压力,单位 BarA(绝压)
|
||
/// - <see cref="LiquidTemperatureC"/>:膨胀阀前液路温度,单位 ℃
|
||
/// </remarks>
|
||
public sealed class PPCSuperheatSubcoolCalculationInput
|
||
{
|
||
/// <summary>
|
||
/// 吸气压力,单位 BarA(绝压)。
|
||
/// </summary>
|
||
public double SuctionPressureBarA { get; set; }
|
||
|
||
/// <summary>
|
||
/// 吸气温度,单位 ℃。
|
||
/// </summary>
|
||
public double SuctionTemperatureC { get; set; }
|
||
|
||
/// <summary>
|
||
/// 液路压力(膨胀阀前压力),单位 BarA(绝压)。
|
||
/// </summary>
|
||
public double LiquidPressureBarA { get; set; }
|
||
|
||
/// <summary>
|
||
/// 液路温度(膨胀阀前温度),单位 ℃。
|
||
/// </summary>
|
||
public double LiquidTemperatureC { get; set; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 过热度 / 过冷度联合计算结果。
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// 本类仅承载计算后的结果值,默认初始化为 <see cref="double.NaN"/>,
|
||
/// 以便调用方区分“尚未赋值”和“计算结果为有效数值”两种状态。
|
||
/// </remarks>
|
||
public sealed class PPCSuperheatSubcoolCalculationResult
|
||
{
|
||
/// <summary>
|
||
/// 过热度结果,单位 K。
|
||
/// 数值公式:吸气实际温度 - 吸气压力对应饱和温度。
|
||
/// </summary>
|
||
public double SuperheatK { get; set; } = double.NaN;
|
||
|
||
/// <summary>
|
||
/// 过冷度结果,单位 K。
|
||
/// 数值公式:液路实际温度 - 液路压力对应饱和温度。
|
||
/// </summary>
|
||
public double SubcoolK { get; set; } = double.NaN;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 过热度 / 过冷度独立计算类。
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// 该类只负责过热度和过冷度的数学/物性计算本身,不直接访问标签,也不负责写回 UI 或实时数据。
|
||
/// 为避免不同计算类共享同一套底层实现后在后续维护中相互影响,
|
||
/// 本类将自身所需的 REFPROP 初始化与饱和温度查询过程内聚在类内私有 support 中。
|
||
///
|
||
/// 当前实现保持与原有 <c>PPCService</c> 中的流程一致:
|
||
/// 1. 压力由 BarA 换算到 MPa
|
||
/// 2. 通过 SATP 按压力求饱和温度 <c>Tsat</c>
|
||
/// 3. 将 REFPROP 返回的 K 温度换算回 ℃
|
||
/// 4. 用“实测温度 - 饱和温度”得到过热度/过冷度
|
||
/// </remarks>
|
||
public sealed class PPCSuperheatSubcoolCalculator
|
||
{
|
||
/// <summary>
|
||
/// 底层物性计算支持对象。
|
||
/// 提供 REFPROP 初始化、SATP 饱和性质查询等公共能力。
|
||
/// </summary>
|
||
private readonly LocalCalculationSupport _support;
|
||
|
||
/// <summary>
|
||
/// 初始化过热度 / 过冷度计算类。
|
||
/// </summary>
|
||
public PPCSuperheatSubcoolCalculator(object refpropLock)
|
||
{
|
||
_support = new LocalCalculationSupport(refpropLock);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 一次性计算过热度和过冷度。
|
||
/// </summary>
|
||
/// <param name="input">输入测点数据,包含吸气侧和液路侧压力/温度。</param>
|
||
/// <param name="result">输出计算结果对象,成功时包含过热度与过冷度。</param>
|
||
/// <param name="error">失败原因。任一子步骤失败时返回具体错误描述。</param>
|
||
/// <returns>
|
||
/// - <see langword="true"/>:过热度和过冷度都计算成功
|
||
/// - <see langword="false"/>:任一结果计算失败
|
||
/// </returns>
|
||
public bool TryCalculate(PPCSuperheatSubcoolCalculationInput input, out PPCSuperheatSubcoolCalculationResult result, out string error)
|
||
{
|
||
// 先创建结果对象,并将错误文本置空,保持 TryXXX 风格的一致性。
|
||
result = new PPCSuperheatSubcoolCalculationResult();
|
||
error = string.Empty;
|
||
|
||
// 先算过热度。
|
||
// 只要过热度失败,就直接结束本次联合计算,并把失败原因透传给调用方。
|
||
if (!TryCalculateSuperheatK(input.SuctionPressureBarA, input.SuctionTemperatureC, out var superheatK, out var superheatErr))
|
||
{
|
||
error = superheatErr;
|
||
return false;
|
||
}
|
||
|
||
// 再算过冷度。
|
||
// 保持与原逻辑一致:两个结果都必须成功,联合计算才算成功。
|
||
if (!TryCalculateSubcoolK(input.LiquidPressureBarA, input.LiquidTemperatureC, out var subcoolK, out var subcoolErr))
|
||
{
|
||
error = subcoolErr;
|
||
return false;
|
||
}
|
||
|
||
// 两个结果均成功后,写入输出对象。
|
||
result.SuperheatK = superheatK;
|
||
result.SubcoolK = subcoolK;
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算过热度,单位 K。
|
||
/// </summary>
|
||
/// <param name="suctionPressureBarA">吸气压力,单位 BarA(绝压)。</param>
|
||
/// <param name="suctionTemperatureC">吸气温度,单位 ℃。</param>
|
||
/// <param name="superheatK">过热度输出,单位 K。</param>
|
||
/// <param name="error">失败原因,如 REFPROP 未初始化、SATP 查询失败或结果异常。</param>
|
||
/// <returns>是否计算成功。</returns>
|
||
/// <remarks>
|
||
/// 计算过程保持与原 <c>PPCService</c> 一致:
|
||
/// 1. 吸气压力由 BarA 转为 MPa
|
||
/// 2. 用 SATP 按压力求出该压力下的饱和温度 <c>Tsat</c>(K)
|
||
/// 3. 把饱和温度换算为 ℃
|
||
/// 4. 过热度 = 吸气实测温度 - 饱和温度
|
||
/// </remarks>
|
||
public bool TryCalculateSuperheatK(double suctionPressureBarA, double suctionTemperatureC, out double superheatK, out string error)
|
||
{
|
||
// 默认输出 NaN,表示当前尚未得到有效结果。
|
||
superheatK = double.NaN;
|
||
error = string.Empty;
|
||
|
||
if (double.IsNaN(suctionPressureBarA) || double.IsInfinity(suctionPressureBarA) || suctionPressureBarA <= 0)
|
||
{
|
||
error = "吸气压力无效";
|
||
return false;
|
||
}
|
||
|
||
// REFPROP 相关函数调用前先做幂等初始化。
|
||
if (!_support.EnsureRefpropInitialized(out var initErr))
|
||
{
|
||
error = initErr;
|
||
return false;
|
||
}
|
||
|
||
// 现有物性 helper 约定输入压力为 MPa,因此这里把 BarA 转成 MPa。
|
||
double pressureMPa = suctionPressureBarA * 0.1;
|
||
|
||
if (pressureMPa <= 0)
|
||
{
|
||
error = "吸气压力无效";
|
||
return false;
|
||
}
|
||
|
||
// 按压力求饱和温度。
|
||
// 本计算只关心 Tsat,因此 Dl/Dv 结果不使用,用 out _ 丢弃。
|
||
if (!_support.TrySATP_SaturationByP_MPa(pressureMPa, out var tSatK, out _, out _, out var satErr))
|
||
{
|
||
error = satErr;
|
||
return false;
|
||
}
|
||
|
||
// REFPROP 返回的饱和温度是 K,这里换算成 ℃后再与实测温度做差。
|
||
// 公式与原实现完全一致:Superheat = T_actual - T_sat。
|
||
superheatK = suctionTemperatureC - (tSatK - 273.15);
|
||
|
||
// 防御性检查:避免把 NaN / Infinity 写回上层标签。
|
||
if (double.IsNaN(superheatK) || double.IsInfinity(superheatK))
|
||
{
|
||
error = "过热度结果异常";
|
||
return false;
|
||
}
|
||
|
||
if (Math.Abs(superheatK) > 100.0)
|
||
{
|
||
error = $"过热度结果超范围: {superheatK}";
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算过冷度,单位 K。
|
||
/// </summary>
|
||
/// <param name="liquidPressureBarA">液路压力(膨胀阀前压力),单位 BarA(绝压)。</param>
|
||
/// <param name="liquidTemperatureC">液路温度(膨胀阀前温度),单位 ℃。</param>
|
||
/// <param name="subcoolK">过冷度输出,单位 K。</param>
|
||
/// <param name="error">失败原因,如 REFPROP 未初始化、SATP 查询失败或结果异常。</param>
|
||
/// <returns>是否计算成功。</returns>
|
||
/// <remarks>
|
||
/// 当前实现与原有逻辑保持一致,公式写法为:
|
||
/// <c>Subcool = T_actual - T_sat</c>
|
||
/// 即“液路实测温度减去对应压力下的饱和温度”。
|
||
/// </remarks>
|
||
public bool TryCalculateSubcoolK(double liquidPressureBarA, double liquidTemperatureC, out double subcoolK, out string error)
|
||
{
|
||
// 默认输出 NaN,表示当前尚未得到有效结果。
|
||
subcoolK = double.NaN;
|
||
error = string.Empty;
|
||
|
||
if (double.IsNaN(liquidPressureBarA) || double.IsInfinity(liquidPressureBarA) || liquidPressureBarA <= 0)
|
||
{
|
||
error = "液路压力无效";
|
||
return false;
|
||
}
|
||
|
||
// REFPROP 相关函数调用前先做幂等初始化。
|
||
if (!_support.EnsureRefpropInitialized(out var initErr))
|
||
{
|
||
error = initErr;
|
||
return false;
|
||
}
|
||
|
||
// 现有 SATP helper 约定压力单位为 MPa,因此先由 BarA 转成 MPa。
|
||
double pressureMPa = liquidPressureBarA * 0.1;
|
||
|
||
if (pressureMPa <= 0)
|
||
{
|
||
error = "液路压力无效";
|
||
return false;
|
||
}
|
||
|
||
// 查询该液路压力下的饱和温度 Tsat。
|
||
if (!_support.TrySATP_SaturationByP_MPa(pressureMPa, out var tSatK, out _, out _, out var satErr))
|
||
{
|
||
error = satErr;
|
||
return false;
|
||
}
|
||
|
||
// 与原实现保持一致:直接用实测液路温度减去饱和温度。
|
||
subcoolK = liquidTemperatureC - (tSatK - 273.15);
|
||
|
||
// 防御性检查:避免异常数值继续上传。
|
||
if (double.IsNaN(subcoolK) || double.IsInfinity(subcoolK))
|
||
{
|
||
error = "过冷度结果异常";
|
||
return false;
|
||
}
|
||
|
||
if (Math.Abs(subcoolK) > 100.0)
|
||
{
|
||
error = $"过冷度结果超范围: {subcoolK}";
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 过热度 / 过冷度计算类私有的底层物性支持实现。
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// 该实现只服务当前计算类,目的是把本类依赖的 REFPROP 调用过程固定在本文件内部,
|
||
/// 避免后续为了其他计算类调整共享 support 时,影响已经验算通过的过热度 / 过冷度结果。
|
||
/// </remarks>
|
||
private sealed class LocalCalculationSupport : IPPCCalculationSupport
|
||
{
|
||
private readonly object _refpropLock;
|
||
private static volatile bool _rpInitialized;
|
||
|
||
public LocalCalculationSupport(object refpropLock)
|
||
{
|
||
_refpropLock = refpropLock ?? throw new ArgumentNullException(nameof(refpropLock));
|
||
}
|
||
|
||
public 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;
|
||
}
|
||
}
|
||
|
||
public 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;
|
||
double Dv = 0;
|
||
double[] xliq = new double[20];
|
||
double[] xvap = new double[20];
|
||
long ierr = 0;
|
||
long 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;
|
||
}
|
||
|
||
public bool TryTPRHO_VaporDensity_ByTP_MPa_C(double pressureMPa, double temperatureC, out double densityMolPerL, out string error) => throw new NotSupportedException();
|
||
|
||
public bool TryTHERM_VaporEntropy_ByTD(double temperatureC, double densityMolPerL, out double entropy_kJ_per_kgK, out string error) => throw new NotSupportedException();
|
||
|
||
public bool TryTPRHO_LiquidDensity_ByTP_MPa_C(double pressureMPa, double temperatureC, out double densityMolPerL, out string error) => throw new NotSupportedException();
|
||
|
||
public bool TryTHERM_LiquidEnthalpy_ByTD(double temperatureC, double densityMolPerL, out double h_liq_kJ_per_kg, out string error) => throw new NotSupportedException();
|
||
|
||
public bool TryTHERM_Enthalpy_kJkg_ByT_K_D(double temperatureK, double densityMolPerL, out double h_kJ_per_kg, out string error) => throw new NotSupportedException();
|
||
|
||
public bool TryConvertMolarDensityToSpecificVolume(double d_molL, out double v_m3kg, out string error) => throw new NotSupportedException();
|
||
|
||
public bool TryGetIsentropicOutletEnthalpy_h2s_ByP2AndS1_BarA(double dischargePressureBarA, double suctionEntropy_kJkgK, out double h2s_kJkg, out string error) => throw new NotSupportedException();
|
||
}
|
||
}
|
||
}
|