添加气瓶计算过程文档
This commit is contained in:
1 parent
f0310b6b40
commit
67b855d7ec
3 files changed
+357
-34
No files matched your search
@@ -0,0 +1,338 @@
|
|||||||
|
## 1. 模型定位
|
||||||
|
|
||||||
|
`gas_cylinder.py` 实现的是高压气瓶的 0D 集总参数模型 `HighPressureGasCylinder`。模型只描述气瓶内部平均热力状态,不解析气瓶内的轴向、径向或局部流场分布。
|
||||||
|
|
||||||
|
主要假设:
|
||||||
|
|
||||||
|
- 气瓶有效容积 `V` 固定。
|
||||||
|
- 气瓶内工质均匀,使用单一平均密度和单一平均比内能描述。
|
||||||
|
- 独立守恒状态只有总质量 `mass` 和总内能 `U`。
|
||||||
|
- 压力、温度和比焓通过 CoolProp 由当前守恒状态恢复。
|
||||||
|
- 模型不自行计算边界流量;外部模型给出质量流率和能量流率后,气瓶只更新守恒量。
|
||||||
|
|
||||||
|
对应代码:`src/cylinder/gas_cylinder.py:20-142`
|
||||||
|
|
||||||
|
## 2. 参数与状态
|
||||||
|
|
||||||
|
默认参数定义在 `src/cylinder/gas_cylinder.py:14-17`:
|
||||||
|
|
||||||
|
| 参数 | 默认值 | 含义 |
|
||||||
|
| --- | ---: | --- |
|
||||||
|
| `DEFAULT_FLUID` | `Helium` | CoolProp 工质名称 |
|
||||||
|
| `DEFAULT_P_INIT` | `18.031e6 Pa` | 初始绝对压力 |
|
||||||
|
| `DEFAULT_VOLUME` | `20e-3 m^3` | 气瓶容积,20 L |
|
||||||
|
| `DEFAULT_T_INIT` | `78.0 K` | 初始温度 |
|
||||||
|
|
||||||
|
构造函数输入为:
|
||||||
|
|
||||||
|
$$
|
||||||
|
\left(\mathrm{fluid},\ P_0,\ V,\ T_0,\ \mathrm{backend}\right)
|
||||||
|
$$
|
||||||
|
|
||||||
|
其中 `backend` 默认是 `HEOS`。构造函数会检查:
|
||||||
|
|
||||||
|
$$
|
||||||
|
\mathrm{fluid}\ne\varnothing,\quad P_0>0,\quad V>0,\quad T_0>0
|
||||||
|
$$
|
||||||
|
|
||||||
|
气瓶模型的独立状态向量为:
|
||||||
|
|
||||||
|
$$
|
||||||
|
\mathbf{y}_{\mathrm{cyl}}=\left[m_{\mathrm{cyl}},\ U_{\mathrm{cyl}}\right]
|
||||||
|
$$
|
||||||
|
|
||||||
|
其中:
|
||||||
|
|
||||||
|
- `mass` 对应 $m_{\mathrm{cyl}}$,单位 kg。
|
||||||
|
- `U` 对应 $U_{\mathrm{cyl}}$,单位 J。
|
||||||
|
- 压力 `P`、温度 `T`、密度 `rho`、比内能 `specific_internal_energy` 和比焓 `h` 都是由这两个守恒量派生出来的量。
|
||||||
|
|
||||||
|
## 3. 初始状态计算
|
||||||
|
|
||||||
|
初始化发生在 `src/cylinder/gas_cylinder.py:51-56`。计算流程如下。
|
||||||
|
|
||||||
|
第一步,建立 CoolProp 状态对象:
|
||||||
|
|
||||||
|
```python
|
||||||
|
self._state = AbstractState(backend, fluid)
|
||||||
|
```
|
||||||
|
|
||||||
|
第二步,用初始压力和初始温度更新热力状态:
|
||||||
|
|
||||||
|
```python
|
||||||
|
self._state.update(CP.PT_INPUTS, P_init, T_init)
|
||||||
|
```
|
||||||
|
|
||||||
|
对应数学表达为:
|
||||||
|
|
||||||
|
$$
|
||||||
|
\mathrm{state}_0=\mathrm{CoolProp}\left(P_0,T_0,\mathrm{fluid},\mathrm{backend}\right)
|
||||||
|
$$
|
||||||
|
|
||||||
|
第三步,从 CoolProp 读取初始密度:
|
||||||
|
|
||||||
|
$$
|
||||||
|
\rho_0=\rho\left(P_0,T_0\right)
|
||||||
|
$$
|
||||||
|
|
||||||
|
代码中对应:
|
||||||
|
|
||||||
|
```python
|
||||||
|
rho = self._state.rhomass()
|
||||||
|
```
|
||||||
|
|
||||||
|
第四步,由固定容积计算气瓶初始总质量:
|
||||||
|
|
||||||
|
$$
|
||||||
|
m_{\mathrm{cyl},0}=\rho_0 V
|
||||||
|
$$
|
||||||
|
|
||||||
|
代码中对应:
|
||||||
|
|
||||||
|
```python
|
||||||
|
self.mass = rho * V
|
||||||
|
```
|
||||||
|
|
||||||
|
第五步,从 CoolProp 读取初始比内能,并计算总内能:
|
||||||
|
|
||||||
|
$$
|
||||||
|
U_{\mathrm{cyl},0}=m_{\mathrm{cyl},0}u_0
|
||||||
|
$$
|
||||||
|
|
||||||
|
其中:
|
||||||
|
|
||||||
|
$$
|
||||||
|
u_0=u\left(P_0,T_0\right)
|
||||||
|
$$
|
||||||
|
|
||||||
|
代码中对应:
|
||||||
|
|
||||||
|
```python
|
||||||
|
self.U = self.mass * self._state.umass()
|
||||||
|
```
|
||||||
|
|
||||||
|
因此,模型初始化后不再单独保存初始压力和初始温度;后续压力和温度都通过当前 `mass`、`U` 和 `V` 重新计算。
|
||||||
|
|
||||||
|
## 4. 状态恢复计算
|
||||||
|
|
||||||
|
运行过程中,模型每次需要热力性质时都会调用 `_update_state()`,代码位置为 `src/cylinder/gas_cylinder.py:58-62`。
|
||||||
|
|
||||||
|
### 4.1 密度
|
||||||
|
|
||||||
|
气瓶体积固定,因此当前平均密度为:
|
||||||
|
|
||||||
|
$$
|
||||||
|
\rho_{\mathrm{cyl}}=\frac{m_{\mathrm{cyl}}}{V}
|
||||||
|
$$
|
||||||
|
|
||||||
|
代码位置:`src/cylinder/gas_cylinder.py:64-67`
|
||||||
|
|
||||||
|
### 4.2 比内能
|
||||||
|
|
||||||
|
当前比内能由总内能除以总质量得到:
|
||||||
|
|
||||||
|
$$
|
||||||
|
u_{\mathrm{cyl}}=\frac{U_{\mathrm{cyl}}}{m_{\mathrm{cyl}}}
|
||||||
|
$$
|
||||||
|
|
||||||
|
代码位置:`src/cylinder/gas_cylinder.py:69-72`
|
||||||
|
|
||||||
|
这一步要求 `mass > 0`。`apply_flux()` 在每次通量更新后会检查质量不能为非正值。
|
||||||
|
|
||||||
|
### 4.3 CoolProp 输入对
|
||||||
|
|
||||||
|
模型用质量密度和质量比内能作为 CoolProp 输入对:
|
||||||
|
|
||||||
|
```python
|
||||||
|
self._state.update(CP.DmassUmass_INPUTS, rho, u)
|
||||||
|
```
|
||||||
|
|
||||||
|
对应数学表达为:
|
||||||
|
|
||||||
|
$$
|
||||||
|
\mathrm{state}=\mathrm{CoolProp}\left(\rho_{\mathrm{cyl}},u_{\mathrm{cyl}}\right)
|
||||||
|
$$
|
||||||
|
|
||||||
|
这里的关键点是:`P` 和 `T` 不是独立积分变量,而是 CoolProp 根据 $(\rho,u)$ 反算出来的热力结果。
|
||||||
|
|
||||||
|
### 4.4 压力、温度和比焓
|
||||||
|
|
||||||
|
状态恢复后,模型提供三个主要派生量:
|
||||||
|
|
||||||
|
$$
|
||||||
|
\begin{aligned}
|
||||||
|
P_{\mathrm{cyl}} &= P\left(\rho_{\mathrm{cyl}},u_{\mathrm{cyl}}\right) \\
|
||||||
|
T_{\mathrm{cyl}} &= T\left(\rho_{\mathrm{cyl}},u_{\mathrm{cyl}}\right) \\
|
||||||
|
h_{\mathrm{cyl}} &= h\left(\rho_{\mathrm{cyl}},u_{\mathrm{cyl}}\right)
|
||||||
|
\end{aligned}
|
||||||
|
$$
|
||||||
|
|
||||||
|
对应代码:
|
||||||
|
|
||||||
|
- `P`:`src/cylinder/gas_cylinder.py:74-77`
|
||||||
|
- `T`:`src/cylinder/gas_cylinder.py:79-82`
|
||||||
|
- `h`:`src/cylinder/gas_cylinder.py:84-87`
|
||||||
|
|
||||||
|
当前模型只保留上述三个热力派生量,不再保留额外的热物性辅助输出。
|
||||||
|
|
||||||
|
## 5. 边界 ghost state 计算
|
||||||
|
|
||||||
|
`ghost_state()` 用于给一维守恒流动模型提供气瓶侧边界状态,代码位置为 `src/cylinder/gas_cylinder.py:89-97`。
|
||||||
|
|
||||||
|
返回向量为:
|
||||||
|
|
||||||
|
$$
|
||||||
|
\mathbf{q}_{\mathrm{ghost}}=
|
||||||
|
\left[
|
||||||
|
\rho_{\mathrm{cyl}},\
|
||||||
|
0,\
|
||||||
|
\rho_{\mathrm{cyl}}u_{\mathrm{cyl}}
|
||||||
|
\right]^T
|
||||||
|
$$
|
||||||
|
|
||||||
|
三个分量分别是:
|
||||||
|
|
||||||
|
| 分量 | 代码表达 | 物理意义 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| $q_1$ | `self.rho` | 质量密度 |
|
||||||
|
| $q_2$ | `0.0` | 动量密度 |
|
||||||
|
| $q_3$ | `self.rho * self.specific_internal_energy` | 总能量密度 |
|
||||||
|
|
||||||
|
由于气瓶是 0D 静止控制体,边界 ghost state 的速度取 0,所以动量密度为 0。总能量密度中也没有宏观动能项:
|
||||||
|
|
||||||
|
$$
|
||||||
|
\rho E = \rho u + \frac{1}{2}\rho v^2 = \rho u \quad \left(v=0\right)
|
||||||
|
$$
|
||||||
|
|
||||||
|
注意:这个 ghost state 使用的是真实流体内能。如果下游管路求解器使用理想气体状态方程,耦合前需要确认状态方程和能量定义是否一致。
|
||||||
|
|
||||||
|
## 6. 边界通量更新计算
|
||||||
|
|
||||||
|
`apply_flux(mdot, edot, dt, sign)` 根据外部边界通量更新气瓶守恒量,代码位置为 `src/cylinder/gas_cylinder.py:99-128`。
|
||||||
|
|
||||||
|
输入量含义:
|
||||||
|
|
||||||
|
| 参数 | 单位 | 含义 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `mdot` | kg/s | 边界质量流率 |
|
||||||
|
| `edot` | W | 边界能量流率 |
|
||||||
|
| `dt` | s | 时间步长 |
|
||||||
|
| `sign` | - | 通量方向符号 |
|
||||||
|
|
||||||
|
方向符号定义为:
|
||||||
|
|
||||||
|
$$
|
||||||
|
s=
|
||||||
|
\begin{cases}
|
||||||
|
-1, & \text{气体从气瓶流出} \\
|
||||||
|
+1, & \text{气体流入气瓶}
|
||||||
|
\end{cases}
|
||||||
|
$$
|
||||||
|
|
||||||
|
离散更新方程为:
|
||||||
|
|
||||||
|
$$
|
||||||
|
\begin{aligned}
|
||||||
|
m_{\mathrm{cyl}}^{n+1} &= m_{\mathrm{cyl}}^{n}+s\dot{m}\Delta t \\
|
||||||
|
U_{\mathrm{cyl}}^{n+1} &= U_{\mathrm{cyl}}^{n}+s\dot{E}\Delta t
|
||||||
|
\end{aligned}
|
||||||
|
$$
|
||||||
|
|
||||||
|
代码中对应:
|
||||||
|
|
||||||
|
```python
|
||||||
|
self.mass += sign * mdot * dt
|
||||||
|
self.U += sign * edot * dt
|
||||||
|
```
|
||||||
|
|
||||||
|
连续形式为:
|
||||||
|
|
||||||
|
$$
|
||||||
|
\begin{aligned}
|
||||||
|
\frac{dm_{\mathrm{cyl}}}{dt} &= s\dot{m} \\
|
||||||
|
\frac{dU_{\mathrm{cyl}}}{dt} &= s\dot{E}
|
||||||
|
\end{aligned}
|
||||||
|
$$
|
||||||
|
|
||||||
|
如果外部模型使用边界比焓计算能量流率,常见形式为:
|
||||||
|
|
||||||
|
$$
|
||||||
|
\dot{E}=\dot{m}h_{\mathrm{boundary}}
|
||||||
|
$$
|
||||||
|
|
||||||
|
但气瓶模型本身不强制 `edot = mdot * h`,只接收外部传入的能量流率。这使它可以用于不同边界模型,但也要求调用方保证质量通量和能量通量的物理一致性。
|
||||||
|
|
||||||
|
### 6.1 数值保护
|
||||||
|
|
||||||
|
通量更新前后包含三类保护:
|
||||||
|
|
||||||
|
$$
|
||||||
|
\Delta t\ge0,\quad s\in\{-1,+1\},\quad m_{\mathrm{cyl}}^{n+1}>0
|
||||||
|
$$
|
||||||
|
|
||||||
|
对应行为:
|
||||||
|
|
||||||
|
- `dt < 0` 时抛出 `ValueError`。
|
||||||
|
- `sign` 不是 `-1` 或 `+1` 时抛出 `ValueError`。
|
||||||
|
- 更新后 `mass <= 0` 时抛出 `RuntimeError`。
|
||||||
|
- 更新完成后立即调用 `_update_state()`,让 CoolProp 立刻检查新的 $(\rho,u)$ 是否处于有效热力状态。
|
||||||
|
|
||||||
|
## 7. 状态汇总
|
||||||
|
|
||||||
|
`state_summary()` 返回当前气瓶状态字典,代码位置为 `src/cylinder/gas_cylinder.py:130-142`。
|
||||||
|
|
||||||
|
返回字段为:
|
||||||
|
|
||||||
|
| 字段 | 含义 |
|
||||||
|
| --- | --- |
|
||||||
|
| `fluid` | 工质名称 |
|
||||||
|
| `V` | 气瓶固定容积 |
|
||||||
|
| `mass` | 当前总质量 |
|
||||||
|
| `rho` | 当前平均密度 |
|
||||||
|
| `P` | 当前绝对压力 |
|
||||||
|
| `T` | 当前温度 |
|
||||||
|
| `U` | 当前总内能 |
|
||||||
|
| `u` | 当前比内能 |
|
||||||
|
| `h` | 当前比焓 |
|
||||||
|
|
||||||
|
该函数主要用于后处理、日志输出或快速检查模型状态。
|
||||||
|
|
||||||
|
## 8. 耦合使用简述
|
||||||
|
|
||||||
|
在低温贮箱和气瓶耦合示例中,气瓶状态通常作为整体 ODE 状态向量的一部分:
|
||||||
|
|
||||||
|
$$
|
||||||
|
\mathbf{y}=\left[m_{\mathrm{liq}},\ U_{\mathrm{liq}},\ T_{\mathrm{ull}},\ m_{\mathrm{cyl}},\ U_{\mathrm{cyl}}\right]
|
||||||
|
$$
|
||||||
|
|
||||||
|
气瓶向贮箱供气时,可写为:
|
||||||
|
|
||||||
|
$$
|
||||||
|
\frac{dm_{\mathrm{cyl}}}{dt}=-\dot{m}_{\mathrm{to\ tank}},\quad
|
||||||
|
\frac{dU_{\mathrm{cyl}}}{dt}=-\dot{E}_{\mathrm{to\ tank}}
|
||||||
|
$$
|
||||||
|
|
||||||
|
并可用气瓶压力相对贮箱工作压力的差值作为终止条件:
|
||||||
|
|
||||||
|
$$
|
||||||
|
g(t,\mathbf{y})=P_{\mathrm{cyl}}-P_{\mathrm{work}}
|
||||||
|
$$
|
||||||
|
|
||||||
|
示例代码位置:`examples/cryo_tank_cylinder_system.py`
|
||||||
|
|
||||||
|
## 9. 运行和测试
|
||||||
|
|
||||||
|
气瓶模型可直接导入使用:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from cylinder import HighPressureGasCylinder
|
||||||
|
|
||||||
|
cylinder = HighPressureGasCylinder()
|
||||||
|
summary = cylinder.state_summary()
|
||||||
|
```
|
||||||
|
|
||||||
|
相关测试覆盖默认状态、可配置输入、状态汇总字段、ghost state、出流通量更新和非法输入校验。测试命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pytest -q tests/test_gas_cylinder.py
|
||||||
|
```
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
High-pressure gas cylinder model with configurable working fluid.
|
High-pressure gas cylinder model with configurable working fluid.
|
||||||
|
|
||||||
The cylinder is a 0D lumped model. Its primary state is total mass and
|
The cylinder is a 0D lumped model. Its primary state is total mass and
|
||||||
total internal energy; pressure, temperature, density, enthalpy, and other
|
total internal energy; pressure, temperature, density, and enthalpy are
|
||||||
thermodynamic quantities are recovered from CoolProp on demand.
|
recovered from CoolProp on demand.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
@@ -86,32 +86,6 @@ class HighPressureGasCylinder:
|
|||||||
"""Specific enthalpy [J/kg]."""
|
"""Specific enthalpy [J/kg]."""
|
||||||
return self._update_state().hmass()
|
return self._update_state().hmass()
|
||||||
|
|
||||||
@property
|
|
||||||
def cp(self):
|
|
||||||
"""Specific heat at constant pressure [J/(kg K)]."""
|
|
||||||
return self._update_state().cpmass()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cv(self):
|
|
||||||
"""Specific heat at constant volume [J/(kg K)]."""
|
|
||||||
return self._update_state().cvmass()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def gamma(self):
|
|
||||||
"""Local heat capacity ratio cp/cv [-]."""
|
|
||||||
return self.cp / self.cv
|
|
||||||
|
|
||||||
@property
|
|
||||||
def R_specific(self):
|
|
||||||
"""Specific gas constant [J/(kg K)]."""
|
|
||||||
state = self._update_state()
|
|
||||||
return state.gas_constant() / state.molar_mass()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def compressibility_factor(self):
|
|
||||||
"""Compressibility factor Z = P/(rho R T) [-]."""
|
|
||||||
return self.P / (self.rho * self.R_specific * self.T)
|
|
||||||
|
|
||||||
def ghost_state(self):
|
def ghost_state(self):
|
||||||
"""Return a stagnant conservative state ``[rho, rho*u, rho*E]``.
|
"""Return a stagnant conservative state ``[rho, rho*u, rho*E]``.
|
||||||
|
|
||||||
@@ -165,7 +139,4 @@ class HighPressureGasCylinder:
|
|||||||
"U": self.U,
|
"U": self.U,
|
||||||
"u": self.specific_internal_energy,
|
"u": self.specific_internal_energy,
|
||||||
"h": self.h,
|
"h": self.h,
|
||||||
"gamma": self.gamma,
|
|
||||||
"R_specific": self.R_specific,
|
|
||||||
"Z": self.compressibility_factor,
|
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import numpy as np
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from cylinder.gas_cylinder import (
|
from cylinder.gas_cylinder import (
|
||||||
@@ -19,8 +18,6 @@ def test_default_high_pressure_cylinder_state_matches_inputs():
|
|||||||
assert abs(cylinder.T - DEFAULT_T_INIT) < 1e-8
|
assert abs(cylinder.T - DEFAULT_T_INIT) < 1e-8
|
||||||
assert cylinder.mass > 0
|
assert cylinder.mass > 0
|
||||||
assert cylinder.U > 0
|
assert cylinder.U > 0
|
||||||
assert cylinder.gamma > 1.0
|
|
||||||
assert cylinder.compressibility_factor > 0.0
|
|
||||||
|
|
||||||
|
|
||||||
def test_cylinder_accepts_configurable_fluid_pressure_volume_temperature():
|
def test_cylinder_accepts_configurable_fluid_pressure_volume_temperature():
|
||||||
@@ -38,6 +35,23 @@ def test_cylinder_accepts_configurable_fluid_pressure_volume_temperature():
|
|||||||
assert cylinder.mass > 0
|
assert cylinder.mass > 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_cylinder_state_summary_omits_unused_thermodynamic_helpers():
|
||||||
|
cylinder = HighPressureGasCylinder()
|
||||||
|
summary = cylinder.state_summary()
|
||||||
|
|
||||||
|
assert set(summary) == {
|
||||||
|
"fluid",
|
||||||
|
"V",
|
||||||
|
"mass",
|
||||||
|
"rho",
|
||||||
|
"P",
|
||||||
|
"T",
|
||||||
|
"U",
|
||||||
|
"u",
|
||||||
|
"h",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_cylinder_ghost_state_is_stagnant_conservative_vector():
|
def test_cylinder_ghost_state_is_stagnant_conservative_vector():
|
||||||
cylinder = HighPressureGasCylinder()
|
cylinder = HighPressureGasCylinder()
|
||||||
ghost = cylinder.ghost_state()
|
ghost = cylinder.ghost_state()
|
||||||
|
|||||||
Reference in new issue
Block a user