代码仓库移植

This commit is contained in:
ljz committed 2026-06-03 15:41:04 +08:00
commit 0f57ba94f3
44 files changed
+7119

No files matched your search

View File
Whitespace-only changes.
+6
View File
@@ -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"))
+47
View File
@@ -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
+79
View File
@@ -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
+67
View File
@@ -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
+78
View File
@@ -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