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

410 lines
17 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 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();
}
}
}