PP物性更改1

This commit is contained in:
2026-03-25 21:46:24 +08:00
parent 0b25f09d9b
commit 2fe82a707f
3 changed files with 1939 additions and 1069 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,368 @@
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()
{
_support = new LocalCalculationSupport();
}
/// <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;
// REFPROP 相关函数调用前先做幂等初始化。
if (!_support.EnsureRefpropInitialized(out var initErr))
{
error = initErr;
return false;
}
// 现有物性 helper 约定输入压力为 MPa因此这里把 BarA 转成 MPa。
double pressureMPa = suctionPressureBarA * 0.1;
// 按压力求饱和温度。
// 本计算只关心 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;
}
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;
// REFPROP 相关函数调用前先做幂等初始化。
if (!_support.EnsureRefpropInitialized(out var initErr))
{
error = initErr;
return false;
}
// 现有 SATP helper 约定压力单位为 MPa因此先由 BarA 转成 MPa。
double pressureMPa = liquidPressureBarA * 0.1;
// 查询该液路压力下的饱和温度 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;
}
return true;
}
/// <summary>
/// 过热度 / 过冷度计算类私有的底层物性支持实现。
/// </summary>
/// <remarks>
/// 该实现只服务当前计算类,目的是把本类依赖的 REFPROP 调用过程固定在本文件内部,
/// 避免后续为了其他计算类调整共享 support 时,影响已经验算通过的过热度 / 过冷度结果。
/// </remarks>
private sealed class LocalCalculationSupport : IPPCCalculationSupport
{
private static readonly object _refpropLock = new object();
private static volatile bool _rpInitialized;
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();
}
}
}

File diff suppressed because it is too large Load Diff