From 4a22ef1334bc5724a6f74da19e7b626b36d34a1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=A2=E4=BA=AC=E6=B3=BD?= Date: Thu, 4 Jun 2026 06:42:29 +0000 Subject: [PATCH] refactor: organize src modules by category --- AGENTS.md | 5 +- README.md | 14 +++- src/README.md | 17 ++++ src/cylinder/__init__.py | 17 ++++ src/{ => cylinder}/gas_cylinder.py | 0 src/main.py | 128 +--------------------------- src/tank_pipe/__init__.py | 1 + src/{ => tank_pipe}/config.py | 0 src/{ => tank_pipe}/friction.py | 0 src/tank_pipe/main.py | 130 +++++++++++++++++++++++++++++ src/{ => tank_pipe}/output.py | 2 +- src/{ => tank_pipe}/pipe.py | 6 +- src/{ => tank_pipe}/riemann.py | 2 +- src/{ => tank_pipe}/solver.py | 0 src/{ => tank_pipe}/tank.py | 2 +- tests/test_gas_cylinder.py | 2 +- tests/test_integration.py | 6 +- tests/test_pipe.py | 2 +- tests/test_riemann.py | 2 +- tests/test_tank.py | 2 +- 20 files changed, 194 insertions(+), 144 deletions(-) create mode 100644 src/cylinder/__init__.py rename src/{ => cylinder}/gas_cylinder.py (100%) create mode 100644 src/tank_pipe/__init__.py rename src/{ => tank_pipe}/config.py (100%) rename src/{ => tank_pipe}/friction.py (100%) create mode 100644 src/tank_pipe/main.py rename src/{ => tank_pipe}/output.py (99%) rename src/{ => tank_pipe}/pipe.py (97%) rename src/{ => tank_pipe}/riemann.py (99%) rename src/{ => tank_pipe}/solver.py (100%) rename src/{ => tank_pipe}/tank.py (99%) diff --git a/AGENTS.md b/AGENTS.md index 901e90b..f340254 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,12 +1,13 @@ # Repository Guidelines ## Project Structure & Module Organization -Core simulation code lives in `src/`. The base pipe blowdown model is split across `src/tank.py`, `src/pipe.py`, `src/riemann.py`, `src/solver.py`, and `src/output.py`, with `src/main.py` as the entry point. The cryogenic tank variant is isolated under `src/cryo_tank/`. Tests mirror that layout in `tests/` and `tests/cryo_tank/`. Use `cases/` for scenario inputs, `docs/` for design notes and generated reports, `scripts/` for utilities, and `results/` for run artifacts. +Core simulation code lives in `src/`. The base pipe blowdown model is grouped under `src/tank_pipe/`, the high-pressure gas cylinder model is under `src/cylinder/`, and the cryogenic tank variant is isolated under `src/cryo_tank/`. `src/main.py` remains a compatibility entry point for the tank-pipe simulation. Tests live in `tests/` and `tests/cryo_tank/`. Use `cases/` for scenario inputs, `docs/` for design notes and generated reports, `scripts/` for utilities, and `results/` for run artifacts. ## Build, Test, and Development Commands There is no checked-in build system; run modules directly from the repository root. -- `python3 src/main.py`: run the 0D-1D tank-pipe simulation and write plots/reports to `results/`. +- `python3 src/main.py`: run the 0D-1D tank-pipe simulation through the compatibility entry point. +- `python3 src/tank_pipe/main.py`: run the 0D-1D tank-pipe simulation from its package entry point. - `python3 src/cryo_tank/main.py`: run the cryogenic LN2 tank simulation. - `pytest -q`: run the full test suite. - `pytest -q tests/test_integration.py`: run the main conservation tests only. diff --git a/README.md b/README.md index ec826c5..9ac035e 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,20 @@ Test repository for pipe system simulation work. ## Structure +- `src/tank_pipe/` 0D-1D tank-pipe blowdown simulation +- `src/cylinder/` high-pressure gas cylinder model +- `src/cryo_tank/` cryogenic LN2 tank simulation +- `src/main.py` compatibility entry point for the tank-pipe simulation - `docs/` design notes and reports -- `src/` source code - `cases/` test cases and input data - `scripts/` utility scripts - `results/` generated outputs and post-processing summaries -## Status +## Common Commands -Initial repository scaffold created. +```bash +python3 src/main.py +python3 src/tank_pipe/main.py +python3 src/cryo_tank/main.py +pytest -q +``` diff --git a/src/README.md b/src/README.md index f09c834..1dbbbfe 100644 --- a/src/README.md +++ b/src/README.md @@ -1,3 +1,20 @@ # src Source code for pipe system simulation models and utilities. + +## Layout + +- `main.py`: compatibility entry point for the tank-pipe simulation. +- `tank_pipe/`: 0D-1D tank-pipe blowdown model, including configuration, tank, pipe, Riemann solver, time solver, and output helpers. +- `cylinder/`: high-pressure gas cylinder model. +- `cryo_tank/`: cryogenic LN2 tank model with helium pressurization, heat leak, solver, output helpers, and module README. + +## Entry Points + +Run from the repository root: + +```bash +python3 src/main.py +python3 src/tank_pipe/main.py +python3 src/cryo_tank/main.py +``` diff --git a/src/cylinder/__init__.py b/src/cylinder/__init__.py new file mode 100644 index 0000000..48fc58f --- /dev/null +++ b/src/cylinder/__init__.py @@ -0,0 +1,17 @@ +"""High-pressure gas cylinder models.""" + +from cylinder.gas_cylinder import ( + DEFAULT_FLUID, + DEFAULT_P_INIT, + DEFAULT_T_INIT, + DEFAULT_VOLUME, + HighPressureGasCylinder, +) + +__all__ = [ + "DEFAULT_FLUID", + "DEFAULT_P_INIT", + "DEFAULT_T_INIT", + "DEFAULT_VOLUME", + "HighPressureGasCylinder", +] diff --git a/src/gas_cylinder.py b/src/cylinder/gas_cylinder.py similarity index 100% rename from src/gas_cylinder.py rename to src/cylinder/gas_cylinder.py diff --git a/src/main.py b/src/main.py index 5772503..d5099fe 100644 --- a/src/main.py +++ b/src/main.py @@ -1,130 +1,6 @@ -# src/main.py -""" -Entry point: assemble tanks + pipe from config constants, run the solver, -verify total mass/energy conservation, persist history, and generate -plots + animation. +"""Compatibility entry point for the tank-pipe simulation.""" -Run from project root: - python3 src/main.py -""" -import os -import sys - -# Ensure imports work when running from project root -_HERE = os.path.dirname(os.path.abspath(__file__)) -sys.path.insert(0, _HERE) - -import numpy as np - -from config import ( - GAMMA, R_GAS, - V1, P1_INIT, T1_INIT, - V2, P2_INIT, T2_INIT, - L, D, N_CELLS, - MU, ROUGHNESS, - T_END, CFL, RIEMANN_SOLVER, - ANIMATION_STRIDE, OUTPUT_DIR, -) -from tank import Tank -from pipe import Pipe -from solver import run -from output import ( - save_history, - plot_tank_pressure, - plot_tank_temperature, - plot_pipe_final_profiles, - make_pipe_animation, - write_summary_report, -) - - -def _total_mass(tank1, tank2, pipe): - pipe_mass = float(np.sum(pipe.W[0, :] * pipe.area * pipe.dx)) - return tank1.mass + tank2.mass + pipe_mass - - -def _total_energy(tank1, tank2, pipe): - pipe_energy = float(np.sum(pipe.W[2, :] * pipe.area * pipe.dx)) - return tank1.U + tank2.U + pipe_energy - - -def main(): - os.makedirs(OUTPUT_DIR, exist_ok=True) - - # --- Assemble --- - tank1 = Tank(V=V1, P_init=P1_INIT, T_init=T1_INIT, gamma=GAMMA, R_gas=R_GAS) - tank2 = Tank(V=V2, P_init=P2_INIT, T_init=T2_INIT, gamma=GAMMA, R_gas=R_GAS) - pipe = Pipe(L=L, D=D, N=N_CELLS, P_init=P2_INIT, T_init=T2_INIT, - gamma=GAMMA, R_gas=R_GAS, mu=MU, roughness=ROUGHNESS, - riemann_solver=RIEMANN_SOLVER) - - m_init = _total_mass(tank1, tank2, pipe) - U_init = _total_energy(tank1, tank2, pipe) - print(f"Initial total mass: {m_init:.6e} kg") - print(f"Initial total energy: {U_init:.6e} J") - print(f"Initial P1 = {tank1.P/1e6:.3f} MPa, P2 = {tank2.P/1e6:.3f} MPa") - print(f"Pipe: L={L} m, D={D*1e3:.1f} mm, N={N_CELLS} cells, dx={pipe.dx*1e3:.1f} mm") - print(f"Riemann solver: {RIEMANN_SOLVER.upper()}") - if MU > 0: - print(f"Friction: mu={MU:.2e} Pa·s, roughness={ROUGHNESS:.2e} m (eps/D={ROUGHNESS/D:.4f})") - else: - print("Friction: OFF") - print(f"Running to t_end={T_END} s with CFL={CFL}...") - print() - - # --- Run --- - history = run(tank1, tank2, pipe, - t_end=T_END, cfl=CFL, - verbose=True, log_every=200) - - n_steps = len(history['t']) - print() - print(f"Simulation complete: {n_steps} steps") - - # --- Conservation sanity check (per spec §6.1, §8) --- - m_final = _total_mass(tank1, tank2, pipe) - U_final = _total_energy(tank1, tank2, pipe) - rel_err_m = abs(m_final - m_init) / m_init - rel_err_U = abs(U_final - U_init) / U_init - print(f"Final total mass: {m_final:.6e} kg (rel err = {rel_err_m:.2e})") - print(f"Final total energy: {U_final:.6e} J (rel err = {rel_err_U:.2e})") - print(f"Final P1 = {tank1.P/1e6:.3f} MPa, P2 = {tank2.P/1e6:.3f} MPa") - assert rel_err_m < 1e-10, f"Total mass not conserved: rel_err={rel_err_m:.2e}" - assert rel_err_U < 1e-10, f"Total energy not conserved: rel_err={rel_err_U:.2e}" - - # --- Persist + visualize --- - save_history(history, pipe, - os.path.join(OUTPUT_DIR, "history.npz"), - GAMMA, R_GAS) - plot_tank_pressure(history, - os.path.join(OUTPUT_DIR, "tank_pressure.png")) - plot_tank_temperature(history, - os.path.join(OUTPUT_DIR, "tank_temperature.png")) - plot_pipe_final_profiles(history, pipe, - os.path.join(OUTPUT_DIR, "pipe_final_profiles.png"), - GAMMA, R_GAS) - make_pipe_animation(history, pipe, - os.path.join(OUTPUT_DIR, "pipe_animation.gif"), - GAMMA, R_GAS, stride=ANIMATION_STRIDE) - write_summary_report( - history, pipe, - os.path.join(OUTPUT_DIR, "summary_report.html"), - GAMMA, R_GAS, - config={ - 'V1': V1, 'P1_INIT': P1_INIT, 'T1_INIT': T1_INIT, - 'V2': V2, 'P2_INIT': P2_INIT, 'T2_INIT': T2_INIT, - 'L': L, 'D': D, 'N_CELLS': N_CELLS, - 'T_END': T_END, 'CFL': CFL, - }, - ) - - print(f"Outputs written to {OUTPUT_DIR}/") - print(f" - history.npz") - print(f" - tank_pressure.png") - print(f" - tank_temperature.png") - print(f" - pipe_final_profiles.png") - print(f" - pipe_animation.gif") - print(f" - summary_report.html") +from tank_pipe.main import main if __name__ == "__main__": diff --git a/src/tank_pipe/__init__.py b/src/tank_pipe/__init__.py new file mode 100644 index 0000000..b3331a2 --- /dev/null +++ b/src/tank_pipe/__init__.py @@ -0,0 +1 @@ +"""0D-1D tank-pipe blowdown simulation package.""" diff --git a/src/config.py b/src/tank_pipe/config.py similarity index 100% rename from src/config.py rename to src/tank_pipe/config.py diff --git a/src/friction.py b/src/tank_pipe/friction.py similarity index 100% rename from src/friction.py rename to src/tank_pipe/friction.py diff --git a/src/tank_pipe/main.py b/src/tank_pipe/main.py new file mode 100644 index 0000000..10a81a5 --- /dev/null +++ b/src/tank_pipe/main.py @@ -0,0 +1,130 @@ +# src/tank_pipe/main.py +""" +Entry point: assemble tanks + pipe from config constants, run the solver, +verify total mass/energy conservation, persist history, and generate +plots + animation. + +Run from project root: + python3 src/tank_pipe/main.py +""" +import os +import sys + +_HERE = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, os.path.dirname(_HERE)) + +import numpy as np + +from tank_pipe.config import ( + GAMMA, R_GAS, + V1, P1_INIT, T1_INIT, + V2, P2_INIT, T2_INIT, + L, D, N_CELLS, + MU, ROUGHNESS, + T_END, CFL, RIEMANN_SOLVER, + ANIMATION_STRIDE, OUTPUT_DIR, +) +from tank_pipe.tank import Tank +from tank_pipe.pipe import Pipe +from tank_pipe.solver import run +from tank_pipe.output import ( + save_history, + plot_tank_pressure, + plot_tank_temperature, + plot_pipe_final_profiles, + make_pipe_animation, + write_summary_report, +) + + +def _total_mass(tank1, tank2, pipe): + pipe_mass = float(np.sum(pipe.W[0, :] * pipe.area * pipe.dx)) + return tank1.mass + tank2.mass + pipe_mass + + +def _total_energy(tank1, tank2, pipe): + pipe_energy = float(np.sum(pipe.W[2, :] * pipe.area * pipe.dx)) + return tank1.U + tank2.U + pipe_energy + + +def main(): + os.makedirs(OUTPUT_DIR, exist_ok=True) + + # --- Assemble --- + tank1 = Tank(V=V1, P_init=P1_INIT, T_init=T1_INIT, gamma=GAMMA, R_gas=R_GAS) + tank2 = Tank(V=V2, P_init=P2_INIT, T_init=T2_INIT, gamma=GAMMA, R_gas=R_GAS) + pipe = Pipe(L=L, D=D, N=N_CELLS, P_init=P2_INIT, T_init=T2_INIT, + gamma=GAMMA, R_gas=R_GAS, mu=MU, roughness=ROUGHNESS, + riemann_solver=RIEMANN_SOLVER) + + m_init = _total_mass(tank1, tank2, pipe) + U_init = _total_energy(tank1, tank2, pipe) + print(f"Initial total mass: {m_init:.6e} kg") + print(f"Initial total energy: {U_init:.6e} J") + print(f"Initial P1 = {tank1.P/1e6:.3f} MPa, P2 = {tank2.P/1e6:.3f} MPa") + print(f"Pipe: L={L} m, D={D*1e3:.1f} mm, N={N_CELLS} cells, dx={pipe.dx*1e3:.1f} mm") + print(f"Riemann solver: {RIEMANN_SOLVER.upper()}") + if MU > 0: + print(f"Friction: mu={MU:.2e} Pa·s, roughness={ROUGHNESS:.2e} m (eps/D={ROUGHNESS/D:.4f})") + else: + print("Friction: OFF") + print(f"Running to t_end={T_END} s with CFL={CFL}...") + print() + + # --- Run --- + history = run(tank1, tank2, pipe, + t_end=T_END, cfl=CFL, + verbose=True, log_every=200) + + n_steps = len(history['t']) + print() + print(f"Simulation complete: {n_steps} steps") + + # --- Conservation sanity check (per spec §6.1, §8) --- + m_final = _total_mass(tank1, tank2, pipe) + U_final = _total_energy(tank1, tank2, pipe) + rel_err_m = abs(m_final - m_init) / m_init + rel_err_U = abs(U_final - U_init) / U_init + print(f"Final total mass: {m_final:.6e} kg (rel err = {rel_err_m:.2e})") + print(f"Final total energy: {U_final:.6e} J (rel err = {rel_err_U:.2e})") + print(f"Final P1 = {tank1.P/1e6:.3f} MPa, P2 = {tank2.P/1e6:.3f} MPa") + assert rel_err_m < 1e-10, f"Total mass not conserved: rel_err={rel_err_m:.2e}" + assert rel_err_U < 1e-10, f"Total energy not conserved: rel_err={rel_err_U:.2e}" + + # --- Persist + visualize --- + save_history(history, pipe, + os.path.join(OUTPUT_DIR, "history.npz"), + GAMMA, R_GAS) + plot_tank_pressure(history, + os.path.join(OUTPUT_DIR, "tank_pressure.png")) + plot_tank_temperature(history, + os.path.join(OUTPUT_DIR, "tank_temperature.png")) + plot_pipe_final_profiles(history, pipe, + os.path.join(OUTPUT_DIR, "pipe_final_profiles.png"), + GAMMA, R_GAS) + make_pipe_animation(history, pipe, + os.path.join(OUTPUT_DIR, "pipe_animation.gif"), + GAMMA, R_GAS, stride=ANIMATION_STRIDE) + write_summary_report( + history, pipe, + os.path.join(OUTPUT_DIR, "summary_report.html"), + GAMMA, R_GAS, + config={ + 'V1': V1, 'P1_INIT': P1_INIT, 'T1_INIT': T1_INIT, + 'V2': V2, 'P2_INIT': P2_INIT, 'T2_INIT': T2_INIT, + 'L': L, 'D': D, 'N_CELLS': N_CELLS, + 'T_END': T_END, 'CFL': CFL, + }, + ) + + print(f"Outputs written to {OUTPUT_DIR}/") + print(f" - history.npz") + print(f" - tank_pressure.png") + print(f" - tank_temperature.png") + print(f" - pipe_final_profiles.png") + print(f" - pipe_animation.gif") + print(f" - summary_report.html") + + +if __name__ == "__main__": + main() diff --git a/src/output.py b/src/tank_pipe/output.py similarity index 99% rename from src/output.py rename to src/tank_pipe/output.py index 99a8ab0..a67d566 100644 --- a/src/output.py +++ b/src/tank_pipe/output.py @@ -1,4 +1,4 @@ -# src/output.py +# src/tank_pipe/output.py """ Output helpers: persistence (.npz), static plots (.png/.html), animation (.gif). Uses matplotlib's Agg backend so it works in headless environments. diff --git a/src/pipe.py b/src/tank_pipe/pipe.py similarity index 97% rename from src/pipe.py rename to src/tank_pipe/pipe.py index 1ef0c36..8d9ea18 100644 --- a/src/pipe.py +++ b/src/tank_pipe/pipe.py @@ -1,4 +1,4 @@ -# src/pipe.py +# src/tank_pipe/pipe.py """ 1D finite-volume pipe for compressible Euler equations: dW/dt + dF(W)/dx = 0 @@ -12,8 +12,8 @@ Discretization: """ import numpy as np -from riemann import hll_flux, get_riemann_solver -from friction import darcy_friction_factor +from tank_pipe.riemann import hll_flux, get_riemann_solver +from tank_pipe.friction import darcy_friction_factor class Pipe: diff --git a/src/riemann.py b/src/tank_pipe/riemann.py similarity index 99% rename from src/riemann.py rename to src/tank_pipe/riemann.py index 0e8a3dc..9414267 100644 --- a/src/riemann.py +++ b/src/tank_pipe/riemann.py @@ -1,4 +1,4 @@ -# src/riemann.py +# src/tank_pipe/riemann.py """ Riemann flux solvers for the 1D compressible Euler equations. diff --git a/src/solver.py b/src/tank_pipe/solver.py similarity index 100% rename from src/solver.py rename to src/tank_pipe/solver.py diff --git a/src/tank.py b/src/tank_pipe/tank.py similarity index 99% rename from src/tank.py rename to src/tank_pipe/tank.py index ec1dd0f..b624fec 100644 --- a/src/tank.py +++ b/src/tank_pipe/tank.py @@ -1,4 +1,4 @@ -# src/tank.py +# src/tank_pipe/tank.py """ 0D lumped-parameter tank for ideal gas. The tank's *primary* state is (mass, U) where U is total internal energy in joules. Pressure, temperature, diff --git a/tests/test_gas_cylinder.py b/tests/test_gas_cylinder.py index 469097a..e38d710 100644 --- a/tests/test_gas_cylinder.py +++ b/tests/test_gas_cylinder.py @@ -1,7 +1,7 @@ import numpy as np import pytest -from gas_cylinder import ( +from cylinder.gas_cylinder import ( DEFAULT_FLUID, DEFAULT_P_INIT, DEFAULT_T_INIT, diff --git a/tests/test_integration.py b/tests/test_integration.py index 55907ab..0c766fb 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -5,9 +5,9 @@ are the load-bearing tests for the "flux doubling" coupling mechanism. """ import numpy as np import pytest -from tank import Tank -from pipe import Pipe -from solver import run +from tank_pipe.tank import Tank +from tank_pipe.pipe import Pipe +from tank_pipe.solver import run GAMMA = 1.4 diff --git a/tests/test_pipe.py b/tests/test_pipe.py index 05dfea8..f66f000 100644 --- a/tests/test_pipe.py +++ b/tests/test_pipe.py @@ -1,7 +1,7 @@ # tests/test_pipe.py import numpy as np import pytest -from pipe import Pipe +from tank_pipe.pipe import Pipe GAMMA = 1.4 diff --git a/tests/test_riemann.py b/tests/test_riemann.py index 1174469..50e999b 100644 --- a/tests/test_riemann.py +++ b/tests/test_riemann.py @@ -1,6 +1,6 @@ # tests/test_riemann.py import numpy as np -from riemann import hll_flux +from tank_pipe.riemann import hll_flux GAMMA = 1.4 diff --git a/tests/test_tank.py b/tests/test_tank.py index a340723..b4eba54 100644 --- a/tests/test_tank.py +++ b/tests/test_tank.py @@ -1,7 +1,7 @@ # tests/test_tank.py import numpy as np import pytest -from tank import Tank +from tank_pipe.tank import Tank GAMMA = 1.4