代码仓库移植
This commit is contained in:
commit
0f57ba94f3
44 files changed
+7119
No files matched your search
Whitespace-only changes.
@@ -0,0 +1,6 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
_ROOT = os.path.dirname(os.path.dirname(_HERE))
|
||||
sys.path.insert(0, os.path.join(_ROOT, "src"))
|
||||
@@ -0,0 +1,47 @@
|
||||
# tests/cryo_tank/test_heat_leak.py
|
||||
"""Tests for heat leak models."""
|
||||
from cryo_tank.heat_leak import HeatLeakModel, MLIHeatLeak, FoamHeatLeak
|
||||
|
||||
|
||||
class TestMLIHeatLeak:
|
||||
|
||||
def test_mli_returns_constant_heat_flux(self):
|
||||
model = MLIHeatLeak(A_total=3.306, q_mli=1.5)
|
||||
Q = model.compute(T_inner=78.0, T_env=300.0)
|
||||
assert abs(Q - 3.306 * 1.5) < 1e-10
|
||||
|
||||
def test_mli_default_q_is_1(self):
|
||||
model = MLIHeatLeak(A_total=3.306)
|
||||
Q = model.compute(T_inner=78.0, T_env=300.0)
|
||||
assert abs(Q - 3.306) < 1e-10
|
||||
|
||||
def test_mli_independent_of_temperature(self):
|
||||
model = MLIHeatLeak(A_total=3.306, q_mli=2.0)
|
||||
Q1 = model.compute(T_inner=78.0, T_env=300.0)
|
||||
Q2 = model.compute(T_inner=80.0, T_env=250.0)
|
||||
assert abs(Q1 - Q2) < 1e-10
|
||||
|
||||
|
||||
class TestFoamHeatLeak:
|
||||
|
||||
def test_foam_constant_k(self):
|
||||
model = FoamHeatLeak(A_total=3.306, k_eff=0.03, delta=0.05)
|
||||
Q = model.compute(T_inner=78.0, T_env=300.0)
|
||||
expected = 3.306 * 0.03 * (300.0 - 78.0) / 0.05
|
||||
assert abs(Q - expected) < 1e-6
|
||||
|
||||
def test_foam_callable_k(self):
|
||||
def k_func(T):
|
||||
return 0.01 + 0.0001 * T # linear k(T)
|
||||
|
||||
model = FoamHeatLeak(A_total=3.306, k_eff=k_func, delta=0.05)
|
||||
Q = model.compute(T_inner=78.0, T_env=300.0)
|
||||
T_mean = (300.0 + 78.0) / 2.0
|
||||
k_at_mean = k_func(T_mean)
|
||||
expected = 3.306 * k_at_mean * (300.0 - 78.0) / 0.05
|
||||
assert abs(Q - expected) < 1e-6
|
||||
|
||||
def test_foam_zero_dT_gives_zero_Q(self):
|
||||
model = FoamHeatLeak(A_total=3.306, k_eff=0.03, delta=0.05)
|
||||
Q = model.compute(T_inner=300.0, T_env=300.0)
|
||||
assert abs(Q) < 1e-10
|
||||
@@ -0,0 +1,79 @@
|
||||
# tests/cryo_tank/test_integration.py
|
||||
"""Integration tests for the cryogenic tank simulation."""
|
||||
import numpy as np
|
||||
from cryo_tank.tank_model import CryoTank
|
||||
from cryo_tank.heat_leak import MLIHeatLeak
|
||||
from cryo_tank.solver import run
|
||||
from cryo_tank.config import (
|
||||
V_TOTAL, H_TANK, P_WORKING, T_INIT, ULLAGE_FRACTION,
|
||||
MDOT_IN_LN2, T_IN_LN2, MDOT_OUT_LN2, T_IN_HE,
|
||||
H_CONV_SURFACE, T_ENV, A_TOTAL,
|
||||
)
|
||||
|
||||
|
||||
def _make_tank(**overrides):
|
||||
"""Create a tank with default config, allowing overrides."""
|
||||
kw = dict(
|
||||
V_total=V_TOTAL, H_tank=H_TANK,
|
||||
P_work=P_WORKING,
|
||||
T_init=T_INIT, ullage_fraction=ULLAGE_FRACTION,
|
||||
mdot_in_ln2=MDOT_IN_LN2, T_in_ln2=T_IN_LN2,
|
||||
mdot_out_ln2=MDOT_OUT_LN2,
|
||||
T_in_he=T_IN_HE,
|
||||
h_conv=H_CONV_SURFACE, T_env=T_ENV,
|
||||
heat_leak_model=MLIHeatLeak(A_total=A_TOTAL, q_mli=1.0),
|
||||
)
|
||||
kw.update(overrides)
|
||||
return CryoTank(**kw)
|
||||
|
||||
|
||||
class TestMassConservation:
|
||||
|
||||
def test_liquid_mass_change_matches_net_flow(self):
|
||||
"""Over a short run, dm_liq should equal (mdot_in - mdot_out) * dt."""
|
||||
tank = _make_tank()
|
||||
history = run(tank, t_end=10.0, max_step=1.0)
|
||||
|
||||
m_liq_0 = history['m_liq'][0]
|
||||
m_liq_f = history['m_liq'][-1]
|
||||
t_f = history['t'][-1]
|
||||
|
||||
expected_dm = (MDOT_IN_LN2 - MDOT_OUT_LN2) * t_f
|
||||
actual_dm = m_liq_f - m_liq_0
|
||||
|
||||
rel_err = abs(actual_dm - expected_dm) / abs(expected_dm)
|
||||
assert rel_err < 1e-6, f"Mass conservation error: rel_err={rel_err:.2e}"
|
||||
|
||||
|
||||
class TestSteadyState:
|
||||
|
||||
def test_zero_flow_zero_leak_is_static(self):
|
||||
"""With no flow and no heat leak, state should not change."""
|
||||
tank = _make_tank(
|
||||
mdot_in_ln2=0.0,
|
||||
mdot_out_ln2=0.0,
|
||||
h_conv=0.0,
|
||||
heat_leak_model=MLIHeatLeak(A_total=A_TOTAL, q_mli=0.0),
|
||||
)
|
||||
history = run(tank, t_end=100.0, max_step=10.0)
|
||||
|
||||
T_liq = history['T_liq']
|
||||
T_ull = history['T_ull']
|
||||
|
||||
assert abs(T_liq[-1] - T_liq[0]) < 0.01, f"T_liq drifted: {T_liq[0]:.3f} -> {T_liq[-1]:.3f}"
|
||||
assert abs(T_ull[-1] - T_ull[0]) < 0.1, f"T_ull drifted: {T_ull[0]:.3f} -> {T_ull[-1]:.3f}"
|
||||
|
||||
|
||||
class TestPhysicalBehavior:
|
||||
|
||||
def test_liquid_level_decreases(self):
|
||||
"""With net outflow, liquid level should decrease."""
|
||||
tank = _make_tank()
|
||||
history = run(tank, t_end=60.0, max_step=5.0)
|
||||
assert history['fill_fraction'][-1] < history['fill_fraction'][0]
|
||||
|
||||
def test_he_flow_rate_positive(self):
|
||||
"""He should always flow in (pressurization), not out."""
|
||||
tank = _make_tank()
|
||||
history = run(tank, t_end=60.0, max_step=5.0)
|
||||
assert np.all(history['mdot_He'] >= -1e-10) # allow tiny numerical noise
|
||||
@@ -0,0 +1,67 @@
|
||||
# tests/cryo_tank/test_properties.py
|
||||
"""Tests for CoolProp property wrappers."""
|
||||
import pytest
|
||||
import sys
|
||||
sys.path.insert(0, "src")
|
||||
|
||||
from cryo_tank.properties import (
|
||||
ln2_rho, ln2_h, ln2_u, ln2_T_from_u,
|
||||
n2_vapor_u, n2_sat_pressure,
|
||||
he_u, he_h, he_cp, he_cv,
|
||||
)
|
||||
from cryo_tank.config import P_WORKING
|
||||
|
||||
|
||||
class TestLN2Properties:
|
||||
"""Liquid nitrogen properties at P = 0.17 MPa."""
|
||||
|
||||
def test_ln2_density_at_78K(self):
|
||||
rho = ln2_rho(78.0, P_WORKING)
|
||||
assert 800 < rho < 810 # ~803 kg/m3
|
||||
|
||||
def test_ln2_enthalpy_at_77K(self):
|
||||
h = ln2_h(77.0, P_WORKING)
|
||||
assert -130000 < h < -110000 # ~-122695 J/kg
|
||||
|
||||
def test_ln2_internal_energy_at_78K(self):
|
||||
u = ln2_u(78.0, P_WORKING)
|
||||
assert -130000 < u < -110000 # ~-120865 J/kg
|
||||
|
||||
def test_ln2_T_from_u_roundtrip(self):
|
||||
T_orig = 78.0
|
||||
u = ln2_u(T_orig, P_WORKING)
|
||||
T_recovered = ln2_T_from_u(u, P_WORKING)
|
||||
assert abs(T_recovered - T_orig) < 0.01
|
||||
|
||||
|
||||
class TestN2VaporProperties:
|
||||
"""N2 vapor properties."""
|
||||
|
||||
def test_n2_sat_pressure_at_78K(self):
|
||||
P_sat = n2_sat_pressure(78.0)
|
||||
assert 0.10e6 < P_sat < 0.12e6 # ~0.1093 MPa
|
||||
|
||||
def test_n2_vapor_internal_energy_at_78K(self):
|
||||
u = n2_vapor_u(78.0)
|
||||
assert 50000 < u < 60000 # ~55547 J/kg
|
||||
|
||||
|
||||
class TestHeliumProperties:
|
||||
"""Helium (ideal gas) properties."""
|
||||
|
||||
def test_he_cp_near_5196(self):
|
||||
cp = he_cp()
|
||||
assert abs(cp - 5196.2) < 10 # monatomic ideal gas
|
||||
|
||||
def test_he_cv_near_3117(self):
|
||||
cv = he_cv()
|
||||
assert abs(cv - 3117.1) < 10
|
||||
|
||||
def test_he_enthalpy_at_100K(self):
|
||||
h = he_h(100.0)
|
||||
# CoolProp gives ~524762 J/kg at 100K
|
||||
assert 500000 < h < 550000
|
||||
|
||||
def test_he_internal_energy_at_78K(self):
|
||||
u = he_u(78.0)
|
||||
assert 200000 < u < 280000 # ~247932 J/kg
|
||||
@@ -0,0 +1,78 @@
|
||||
# tests/cryo_tank/test_tank_model.py
|
||||
"""Tests for CryoTank model."""
|
||||
import pytest
|
||||
import numpy as np
|
||||
from cryo_tank.tank_model import CryoTank
|
||||
from cryo_tank.heat_leak import MLIHeatLeak
|
||||
from cryo_tank.config import (
|
||||
V_TOTAL, H_TANK, A_CROSS, A_TOTAL, P_WORKING,
|
||||
T_INIT, ULLAGE_FRACTION,
|
||||
MDOT_IN_LN2, T_IN_LN2, MDOT_OUT_LN2, T_IN_HE,
|
||||
H_CONV_SURFACE, T_ENV,
|
||||
)
|
||||
|
||||
|
||||
def _make_tank():
|
||||
"""Create a CryoTank with default config and MLI heat leak."""
|
||||
heat_leak = MLIHeatLeak(A_total=A_TOTAL, q_mli=1.0)
|
||||
return CryoTank(
|
||||
V_total=V_TOTAL, H_tank=H_TANK,
|
||||
P_work=P_WORKING,
|
||||
T_init=T_INIT, ullage_fraction=ULLAGE_FRACTION,
|
||||
mdot_in_ln2=MDOT_IN_LN2, T_in_ln2=T_IN_LN2,
|
||||
mdot_out_ln2=MDOT_OUT_LN2,
|
||||
T_in_he=T_IN_HE,
|
||||
h_conv=H_CONV_SURFACE, T_env=T_ENV,
|
||||
heat_leak_model=heat_leak,
|
||||
)
|
||||
|
||||
|
||||
class TestGeometry:
|
||||
|
||||
def test_cross_section_area(self):
|
||||
tank = _make_tank()
|
||||
assert abs(tank.A_cross - 0.8402) < 0.001
|
||||
|
||||
def test_total_surface_area(self):
|
||||
tank = _make_tank()
|
||||
assert abs(tank.A_total - 3.305) < 0.01
|
||||
|
||||
def test_wetted_area_at_70_percent_fill(self):
|
||||
tank = _make_tank()
|
||||
level = 0.7 * H_TANK # 0.35 m
|
||||
A_wet, A_dry = tank.wetted_areas(level)
|
||||
# A_wet = bottom cap + side * level
|
||||
expected_wet = A_CROSS + np.pi * tank.D * level
|
||||
assert abs(A_wet - expected_wet) < 0.01
|
||||
assert abs(A_wet + A_dry - A_TOTAL) < 0.01
|
||||
|
||||
|
||||
class TestInitialState:
|
||||
|
||||
def test_initial_liquid_mass(self):
|
||||
tank = _make_tank()
|
||||
y0 = tank.initial_state()
|
||||
m_liq = y0[0]
|
||||
# rho_LN2(78K, 0.17MPa) ~ 803.3 kg/m3, V_liq = 0.2941 m3
|
||||
assert 235 < m_liq < 237 # ~236.25 kg
|
||||
|
||||
def test_initial_fill_fraction(self):
|
||||
tank = _make_tank()
|
||||
y0 = tank.initial_state()
|
||||
info = tank.derive(y0)
|
||||
assert abs(info['fill_fraction'] - 0.70) < 0.01
|
||||
|
||||
def test_initial_temperatures(self):
|
||||
tank = _make_tank()
|
||||
y0 = tank.initial_state()
|
||||
info = tank.derive(y0)
|
||||
assert abs(info['T_liq'] - T_INIT) < 0.1
|
||||
assert abs(info['T_ull'] - T_INIT) < 1.0
|
||||
|
||||
def test_initial_pressure_components_sum_to_P_working(self):
|
||||
tank = _make_tank()
|
||||
y0 = tank.initial_state()
|
||||
info = tank.derive(y0)
|
||||
P_N2 = info['P_N2']
|
||||
P_He = info['P_He']
|
||||
assert abs(P_N2 + P_He - P_WORKING) / P_WORKING < 1e-6
|
||||
Reference in new issue
Block a user