aeolus1d 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (454) hide show
  1. aeolus1d/__init__.py +43 -0
  2. aeolus1d/_compat.py +94 -0
  3. aeolus1d/_numba_flags.py +189 -0
  4. aeolus1d/_resources.py +57 -0
  5. aeolus1d/_tables.py +109 -0
  6. aeolus1d/_types.py +103 -0
  7. aeolus1d/analysis/__init__.py +61 -0
  8. aeolus1d/analysis/acoustics.py +879 -0
  9. aeolus1d/analysis/acoustics_tm.py +172 -0
  10. aeolus1d/analysis/cfl_inspector.py +406 -0
  11. aeolus1d/analysis/core_zone_probe.py +536 -0
  12. aeolus1d/analysis/energy_audit.py +1123 -0
  13. aeolus1d/analysis/engine_metrics.py +626 -0
  14. aeolus1d/analysis/linearize.py +1364 -0
  15. aeolus1d/analysis/nox_post.py +457 -0
  16. aeolus1d/analysis/random_cycle.py +189 -0
  17. aeolus1d/analysis/sensitivities.py +199 -0
  18. aeolus1d/analysis/system_reports.py +433 -0
  19. aeolus1d/analysis/turbo_map_viz.py +367 -0
  20. aeolus1d/analysis/vehicle_tasks.py +200 -0
  21. aeolus1d/audio.py +286 -0
  22. aeolus1d/battery_ecm.py +164 -0
  23. aeolus1d/bc/__init__.py +103 -0
  24. aeolus1d/bc/base.py +197 -0
  25. aeolus1d/bc/egr_valve.py +211 -0
  26. aeolus1d/bc/mass_flow_inlet.py +134 -0
  27. aeolus1d/bc/nonreflecting.py +96 -0
  28. aeolus1d/bc/orifice_flow.py +209 -0
  29. aeolus1d/bc/pressure_outlet.py +103 -0
  30. aeolus1d/bc/stagnation_inlet.py +145 -0
  31. aeolus1d/bc/throttle.py +171 -0
  32. aeolus1d/bc/transient.py +272 -0
  33. aeolus1d/bc/valve.py +151 -0
  34. aeolus1d/bc/valve_orifice.py +230 -0
  35. aeolus1d/bc/velocity_inlet.py +96 -0
  36. aeolus1d/bench/__init__.py +1 -0
  37. aeolus1d/bench/abgrall_interface.py +156 -0
  38. aeolus1d/bench/colebrook.py +89 -0
  39. aeolus1d/bench/helmholtz.py +148 -0
  40. aeolus1d/bench/helmholtz_quasi2d.py +161 -0
  41. aeolus1d/bench/helmholtz_resonator.py +108 -0
  42. aeolus1d/bench/lax.py +72 -0
  43. aeolus1d/bench/moody.py +94 -0
  44. aeolus1d/bench/nozzle.py +154 -0
  45. aeolus1d/bench/porous_plug.py +106 -0
  46. aeolus1d/bench/riemann_suite.py +255 -0
  47. aeolus1d/bench/shock_reflection.py +115 -0
  48. aeolus1d/bench/shu_osher.py +123 -0
  49. aeolus1d/bench/smooth_advection.py +257 -0
  50. aeolus1d/bench/sod.py +185 -0
  51. aeolus1d/bench/tjunction.py +111 -0
  52. aeolus1d/bench/tjunction_cp.py +89 -0
  53. aeolus1d/bench/woodward_colella.py +98 -0
  54. aeolus1d/chemistry/__init__.py +19 -0
  55. aeolus1d/chemistry/burcat_db.py +214 -0
  56. aeolus1d/chemistry/db.py +309 -0
  57. aeolus1d/chemistry/db_data/CH4.npz +0 -0
  58. aeolus1d/chemistry/db_data/CO.npz +0 -0
  59. aeolus1d/chemistry/db_data/CO2.npz +0 -0
  60. aeolus1d/chemistry/db_data/H.npz +0 -0
  61. aeolus1d/chemistry/db_data/H2.npz +0 -0
  62. aeolus1d/chemistry/db_data/H2O.npz +0 -0
  63. aeolus1d/chemistry/db_data/H2O2.npz +0 -0
  64. aeolus1d/chemistry/db_data/HO2.npz +0 -0
  65. aeolus1d/chemistry/db_data/N2.npz +0 -0
  66. aeolus1d/chemistry/db_data/N2_janaf_sample.dat +21 -0
  67. aeolus1d/chemistry/db_data/NH3.npz +0 -0
  68. aeolus1d/chemistry/db_data/NO.npz +0 -0
  69. aeolus1d/chemistry/db_data/O.npz +0 -0
  70. aeolus1d/chemistry/db_data/O2.npz +0 -0
  71. aeolus1d/chemistry/db_data/OH.npz +0 -0
  72. aeolus1d/chemistry/equilibrium.py +211 -0
  73. aeolus1d/chemistry/fuel_stoichiometry.py +157 -0
  74. aeolus1d/chemistry/importers/__init__.py +15 -0
  75. aeolus1d/chemistry/importers/burcat.py +230 -0
  76. aeolus1d/chemistry/importers/cantera_yaml.py +943 -0
  77. aeolus1d/chemistry/importers/chemkin_mech.py +828 -0
  78. aeolus1d/chemistry/importers/janaf.py +229 -0
  79. aeolus1d/chemistry/importers/nasa7.py +317 -0
  80. aeolus1d/chemistry/kinetics.py +1049 -0
  81. aeolus1d/chemistry/kinetics_njit.py +615 -0
  82. aeolus1d/chemistry/mech_db/ammonia_scr_demo.json +71 -0
  83. aeolus1d/chemistry/mech_db/h2_oxidation_demo.json +168 -0
  84. aeolus1d/chemistry/mech_db.py +221 -0
  85. aeolus1d/chemistry/mixture.py +725 -0
  86. aeolus1d/chemistry/reaction.py +276 -0
  87. aeolus1d/chemistry/species.py +180 -0
  88. aeolus1d/chemistry/surface_kinetics.py +252 -0
  89. aeolus1d/cli/__init__.py +1 -0
  90. aeolus1d/cli/_shared.py +29 -0
  91. aeolus1d/cli/commands/__init__.py +61 -0
  92. aeolus1d/cli/commands/acoustics.py +137 -0
  93. aeolus1d/cli/commands/analyze_cfl.py +44 -0
  94. aeolus1d/cli/commands/auci_compare.py +54 -0
  95. aeolus1d/cli/commands/audio.py +83 -0
  96. aeolus1d/cli/commands/bench.py +59 -0
  97. aeolus1d/cli/commands/cycle_settle.py +215 -0
  98. aeolus1d/cli/commands/drive_cycle.py +130 -0
  99. aeolus1d/cli/commands/examples.py +75 -0
  100. aeolus1d/cli/commands/expand.py +74 -0
  101. aeolus1d/cli/commands/export.py +36 -0
  102. aeolus1d/cli/commands/export_fmu.py +31 -0
  103. aeolus1d/cli/commands/extract.py +50 -0
  104. aeolus1d/cli/commands/gui.py +66 -0
  105. aeolus1d/cli/commands/import_.py +47 -0
  106. aeolus1d/cli/commands/inspect_gtm.py +35 -0
  107. aeolus1d/cli/commands/modes.py +174 -0
  108. aeolus1d/cli/commands/optimize.py +88 -0
  109. aeolus1d/cli/commands/parallel.py +63 -0
  110. aeolus1d/cli/commands/run.py +967 -0
  111. aeolus1d/cli/commands/sensitivity.py +116 -0
  112. aeolus1d/cli/commands/thermo_db.py +76 -0
  113. aeolus1d/cli/commands/turbo_map.py +99 -0
  114. aeolus1d/cli/commands/warmup.py +154 -0
  115. aeolus1d/cli/doctor.py +242 -0
  116. aeolus1d/cli/main.py +127 -0
  117. aeolus1d/cli/registry.py +102 -0
  118. aeolus1d/closures/__init__.py +5 -0
  119. aeolus1d/closures/_extras_layout.py +81 -0
  120. aeolus1d/closures/bend_loss.py +86 -0
  121. aeolus1d/closures/catalyst.py +705 -0
  122. aeolus1d/closures/catalyst_eos.py +212 -0
  123. aeolus1d/closures/chem_linop.py +111 -0
  124. aeolus1d/closures/chem_surrogate.py +97 -0
  125. aeolus1d/closures/diesel_predictive_combustion.py +214 -0
  126. aeolus1d/closures/doc_sulfur.py +246 -0
  127. aeolus1d/closures/eos.py +529 -0
  128. aeolus1d/closures/eos_njit.py +791 -0
  129. aeolus1d/closures/eos_tabulated.py +275 -0
  130. aeolus1d/closures/eos_tabulated_njit.py +636 -0
  131. aeolus1d/closures/film_transfer.py +106 -0
  132. aeolus1d/closures/filter.py +1357 -0
  133. aeolus1d/closures/flame_geometry.py +92 -0
  134. aeolus1d/closures/friction.py +360 -0
  135. aeolus1d/closures/gas.py +63 -0
  136. aeolus1d/closures/hc_oxidation.py +300 -0
  137. aeolus1d/closures/heat.py +370 -0
  138. aeolus1d/closures/hnco_hydrolysis.py +136 -0
  139. aeolus1d/closures/incylinder_turbulence.py +65 -0
  140. aeolus1d/closures/isat_chem.py +141 -0
  141. aeolus1d/closures/laminar_flame_speed.py +101 -0
  142. aeolus1d/closures/learned_junction_bc.py +292 -0
  143. aeolus1d/closures/learned_models/junction_bc_resistive.npz +0 -0
  144. aeolus1d/closures/learned_models/turbo_map_centrifugal.npz +0 -0
  145. aeolus1d/closures/learned_turbo_map.py +378 -0
  146. aeolus1d/closures/mechanism.py +524 -0
  147. aeolus1d/closures/monolith.py +173 -0
  148. aeolus1d/closures/multi_stage_wiebe.py +404 -0
  149. aeolus1d/closures/pinn.py +211 -0
  150. aeolus1d/closures/pollutants.py +549 -0
  151. aeolus1d/closures/porous.py +509 -0
  152. aeolus1d/closures/reaction_demo.py +232 -0
  153. aeolus1d/closures/reaction_demo_njit.py +35 -0
  154. aeolus1d/closures/registry.py +198 -0
  155. aeolus1d/closures/scr_kinetics.py +449 -0
  156. aeolus1d/closures/scr_storage.py +388 -0
  157. aeolus1d/closures/si_turbulent_combustion.py +161 -0
  158. aeolus1d/closures/species_contract.py +186 -0
  159. aeolus1d/closures/thiele.py +225 -0
  160. aeolus1d/closures/transport.py +136 -0
  161. aeolus1d/closures/twc.py +433 -0
  162. aeolus1d/closures/twc_brinkmeier.py +1237 -0
  163. aeolus1d/closures/urea_decomposition.py +177 -0
  164. aeolus1d/closures/user_code.py +359 -0
  165. aeolus1d/closures/wall_coolant.py +165 -0
  166. aeolus1d/closures/wall_thermal.py +411 -0
  167. aeolus1d/components/__init__.py +90 -0
  168. aeolus1d/components/coolant_pipe.py +207 -0
  169. aeolus1d/components/cylinder.py +3374 -0
  170. aeolus1d/components/helmholtz_resonator.py +231 -0
  171. aeolus1d/components/junction.py +1219 -0
  172. aeolus1d/components/junction_cp.py +519 -0
  173. aeolus1d/components/junction_ehr.py +308 -0
  174. aeolus1d/components/junction_passthrough.py +276 -0
  175. aeolus1d/components/pipe.py +1024 -0
  176. aeolus1d/components/protocols.py +124 -0
  177. aeolus1d/components/species_coupling.py +390 -0
  178. aeolus1d/components/turbo.py +1832 -0
  179. aeolus1d/components/turbo_csv.py +185 -0
  180. aeolus1d/components/turbo_data/example_centrifugal.csv +37 -0
  181. aeolus1d/components/turbo_data/example_centrifugal.npz +0 -0
  182. aeolus1d/components/valve_flow.py +187 -0
  183. aeolus1d/components/volume.py +415 -0
  184. aeolus1d/constants.py +9 -0
  185. aeolus1d/control/__init__.py +79 -0
  186. aeolus1d/control/boost_controller.py +89 -0
  187. aeolus1d/control/dpf_heater_controller.py +100 -0
  188. aeolus1d/control/egr_valve_controller.py +138 -0
  189. aeolus1d/control/epw_closed_loop.py +282 -0
  190. aeolus1d/control/fuel_injector_controller.py +176 -0
  191. aeolus1d/control/pid_tune.py +228 -0
  192. aeolus1d/control/signal_toolbox.py +327 -0
  193. aeolus1d/control/spark_timing_controller.py +159 -0
  194. aeolus1d/control/step_response.py +181 -0
  195. aeolus1d/control/wastegate_controller.py +127 -0
  196. aeolus1d/coolant.py +147 -0
  197. aeolus1d/doe.py +223 -0
  198. aeolus1d/drive_cycles.py +208 -0
  199. aeolus1d/driveline.py +360 -0
  200. aeolus1d/driver.py +200 -0
  201. aeolus1d/emotor.py +179 -0
  202. aeolus1d/energy_management.py +158 -0
  203. aeolus1d/engine_oil.py +147 -0
  204. aeolus1d/engine_post.py +492 -0
  205. aeolus1d/examples/01_single_pipe_sod.toml +41 -0
  206. aeolus1d/examples/02_cd_nozzle.toml +43 -0
  207. aeolus1d/examples/03_two_pipes_junction.toml +67 -0
  208. aeolus1d/examples/04_pulsed_intake.toml +41 -0
  209. aeolus1d/examples/05_catalyst_plug.toml +50 -0
  210. aeolus1d/examples/06_helmholtz_quasi2d.toml +57 -0
  211. aeolus1d/examples/07_catalyst_thermal_front.toml +58 -0
  212. aeolus1d/examples/08_reactive_pipe.toml +49 -0
  213. aeolus1d/examples/09_real_gas_shock_tube.toml +58 -0
  214. aeolus1d/examples/10_cp_junction_kloss.toml +82 -0
  215. aeolus1d/examples/11_neck_pipe_junction.toml +87 -0
  216. aeolus1d/examples/12_cp_junction.toml +81 -0
  217. aeolus1d/examples/13_ehr_helmholtz.toml +45 -0
  218. aeolus1d/examples/14_py_junction_bc.toml +48 -0
  219. aeolus1d/examples/15_strang_chemistry.toml +55 -0
  220. aeolus1d/examples/16_bs_chemistry.toml +56 -0
  221. aeolus1d/examples/17_real_gas_nozzle.toml +60 -0
  222. aeolus1d/examples/18_real_gas_catalyst.toml +65 -0
  223. aeolus1d/examples/19_real_gas_plenum.toml +80 -0
  224. aeolus1d/examples/20_multi_species.toml +53 -0
  225. aeolus1d/examples/21_multi_species_kinetics.toml +56 -0
  226. aeolus1d/examples/22_lodi_port.toml +55 -0
  227. aeolus1d/examples/23_mp5_sod.toml +69 -0
  228. aeolus1d/examples/24_hllc_lm_pulse.toml +61 -0
  229. aeolus1d/examples/25_valve_inlet.toml +55 -0
  230. aeolus1d/examples/26_valve_orifice.toml +54 -0
  231. aeolus1d/examples/27_compressor.toml +71 -0
  232. aeolus1d/examples/28_turbo_shaft.toml +126 -0
  233. aeolus1d/examples/29_learned_junction_bc.toml +83 -0
  234. aeolus1d/examples/30_learned_turbo_map.toml +74 -0
  235. aeolus1d/examples/31_cylinder.toml +104 -0
  236. aeolus1d/examples/35_mechanism_pipe.toml +56 -0
  237. aeolus1d/examples/38_engine_turbo_scr.toml +238 -0
  238. aeolus1d/examples/39_4cyl_engine_turbo_scr.toml +669 -0
  239. aeolus1d/examples/40_implicit_adaptive_pipe.toml +64 -0
  240. aeolus1d/examples/41_dpf_regen.toml +111 -0
  241. aeolus1d/examples/42_scr_urea_injector.toml +172 -0
  242. aeolus1d/examples/43_scrf.toml +217 -0
  243. aeolus1d/examples/44_egr_cooler.toml +56 -0
  244. aeolus1d/examples/45_two_stage_scr.toml +212 -0
  245. aeolus1d/examples/46_doc.toml +124 -0
  246. aeolus1d/examples/48_hnco_hydrolysis.toml +124 -0
  247. aeolus1d/examples/49_dpf_no_inhibition.toml +101 -0
  248. aeolus1d/examples/50_dpf_active_regen.toml +107 -0
  249. aeolus1d/examples/50_two_cyl_engine.toml +279 -0
  250. aeolus1d/examples/51_vgt_boost_ctrl.toml +135 -0
  251. aeolus1d/examples/52_knock_event.toml +127 -0
  252. aeolus1d/examples/53_dpf_heater_ctrl.toml +117 -0
  253. aeolus1d/examples/54_adaptive_picard_turbo.toml +159 -0
  254. aeolus1d/examples/55_dpf_regen_hrr.toml +100 -0
  255. aeolus1d/examples/55_vgt_3d_turbo.toml +154 -0
  256. aeolus1d/examples/56_dpf_regen_lowmach.toml +104 -0
  257. aeolus1d/examples/57_picard_stiffness_step.toml +189 -0
  258. aeolus1d/examples/58_tabulated_eos_implicit.toml +73 -0
  259. aeolus1d/examples/59_wastegate_boost_ctrl.toml +148 -0
  260. aeolus1d/examples/60_twin_sequential_turbo.toml +235 -0
  261. aeolus1d/examples/61_doc_sulfur_poisoning.toml +167 -0
  262. aeolus1d/examples/62_egr_valve_ctrl.toml +98 -0
  263. aeolus1d/examples/63_spark_timing_ctrl.toml +139 -0
  264. aeolus1d/examples/71_ktm_510_si.toml +256 -0
  265. aeolus1d/examples/72_throttle_demo.toml +76 -0
  266. aeolus1d/examples/74_twc.toml +226 -0
  267. aeolus1d/examples/75_coolant_pipe.toml +67 -0
  268. aeolus1d/examples/76_wltp_lite_dpf.toml +194 -0
  269. aeolus1d/examples/77_pipe_flow_heat.toml +57 -0
  270. aeolus1d/examples/78_pipe_bend_loss.toml +53 -0
  271. aeolus1d/examples/79_orifice_plate.toml +57 -0
  272. aeolus1d/examples/80_pipe_wall_warmup.toml +60 -0
  273. aeolus1d/examples/81_bend_geometric.toml +50 -0
  274. aeolus1d/examples/82_multilayer_wall.toml +72 -0
  275. aeolus1d/examples/83_turbo_robustness.toml +145 -0
  276. aeolus1d/examples/84_dpf_substrate_inertia.toml +80 -0
  277. aeolus1d/examples/85_vehicle_drive_cycle.toml +65 -0
  278. aeolus1d/examples/86_electrified_drive_cycle.toml +56 -0
  279. aeolus1d/examples/87_multicomponent_variable_gamma_shock_tube.toml +69 -0
  280. aeolus1d/examples/88_dd13_diesel.toml +115 -0
  281. aeolus1d/examples/89_epw_diesel.toml +1025 -0
  282. aeolus1d/examples/90_variable_cv_real_gas.toml +72 -0
  283. aeolus1d/examples/91_cylinder_species_transport.toml +232 -0
  284. aeolus1d/fmi.py +325 -0
  285. aeolus1d/fuel_map.py +80 -0
  286. aeolus1d/fuels.py +107 -0
  287. aeolus1d/homotopy.py +216 -0
  288. aeolus1d/hybrid.py +313 -0
  289. aeolus1d/io/__init__.py +47 -0
  290. aeolus1d/io/_ignition_mode.py +104 -0
  291. aeolus1d/io/_kind_codec.py +286 -0
  292. aeolus1d/io/build.py +3451 -0
  293. aeolus1d/io/case.py +939 -0
  294. aeolus1d/io/case_upgrades.py +227 -0
  295. aeolus1d/io/cgns_writer.py +296 -0
  296. aeolus1d/io/expand_case.py +302 -0
  297. aeolus1d/io/interop/__init__.py +197 -0
  298. aeolus1d/io/interop/_xml_common.py +151 -0
  299. aeolus1d/io/interop/avl_boost.py +140 -0
  300. aeolus1d/io/interop/avl_cruise_m/__init__.py +107 -0
  301. aeolus1d/io/interop/avl_cruise_m/_calibration.py +1405 -0
  302. aeolus1d/io/interop/avl_cruise_m/_core.py +3937 -0
  303. aeolus1d/io/interop/avl_cruise_m/_geometry.py +683 -0
  304. aeolus1d/io/interop/avl_cruise_m/_porous.py +601 -0
  305. aeolus1d/io/interop/avl_cruise_m/_urea.py +467 -0
  306. aeolus1d/io/interop/avl_cruise_m/types.py +140 -0
  307. aeolus1d/io/interop/avl_cruise_m/units.py +212 -0
  308. aeolus1d/io/interop/avl_cruise_m_tune.py +1004 -0
  309. aeolus1d/io/interop/btc_turbo_map.py +767 -0
  310. aeolus1d/io/interop/extract.py +247 -0
  311. aeolus1d/io/interop/fmu.py +440 -0
  312. aeolus1d/io/interop/gt_maps.py +393 -0
  313. aeolus1d/io/interop/gt_params.py +207 -0
  314. aeolus1d/io/interop/gt_power.py +171 -0
  315. aeolus1d/io/interop/gt_suite_native.py +2319 -0
  316. aeolus1d/io/interop/openwam.py +336 -0
  317. aeolus1d/io/interop/schematic.py +795 -0
  318. aeolus1d/io/interop/species_db.py +237 -0
  319. aeolus1d/io/interop/ucp.py +434 -0
  320. aeolus1d/io/interop/wave.py +145 -0
  321. aeolus1d/io/manifest.py +152 -0
  322. aeolus1d/io/parsers.py +1819 -0
  323. aeolus1d/io/resume.py +749 -0
  324. aeolus1d/io/schema.py +3468 -0
  325. aeolus1d/io/schema_help.py +2636 -0
  326. aeolus1d/io/schema_upgrades.py +166 -0
  327. aeolus1d/io/snapshot.py +399 -0
  328. aeolus1d/io/source_pos.py +202 -0
  329. aeolus1d/io/tracer.py +509 -0
  330. aeolus1d/io/validate.py +2443 -0
  331. aeolus1d/io/vehicle_case.py +566 -0
  332. aeolus1d/io/writer.py +552 -0
  333. aeolus1d/io/xdmf.py +309 -0
  334. aeolus1d/logging.py +193 -0
  335. aeolus1d/modes/__init__.py +65 -0
  336. aeolus1d/modes/assemble.py +130 -0
  337. aeolus1d/modes/elements.py +168 -0
  338. aeolus1d/modes/network.py +371 -0
  339. aeolus1d/modes/solve.py +125 -0
  340. aeolus1d/notebook.py +182 -0
  341. aeolus1d/optimize.py +811 -0
  342. aeolus1d/parallel.py +139 -0
  343. aeolus1d/planetary.py +71 -0
  344. aeolus1d/py.typed +0 -0
  345. aeolus1d/regen.py +58 -0
  346. aeolus1d/runtime/__init__.py +8 -0
  347. aeolus1d/runtime/cfl_ramp.py +84 -0
  348. aeolus1d/runtime/live_control.py +270 -0
  349. aeolus1d/scaffold/__init__.py +20 -0
  350. aeolus1d/scaffold/engine.py +302 -0
  351. aeolus1d/scaffold/thermal_network.py +169 -0
  352. aeolus1d/scaffold/turbo_sizing.py +253 -0
  353. aeolus1d/scaffold/wizards.py +175 -0
  354. aeolus1d/sensitivity.py +158 -0
  355. aeolus1d/solver/__init__.py +1 -0
  356. aeolus1d/solver/chem_bs_brinkmeier.py +946 -0
  357. aeolus1d/solver/chem_bs_jit.py +366 -0
  358. aeolus1d/solver/chem_diagnostics.py +252 -0
  359. aeolus1d/solver/chem_numbalsoda.py +360 -0
  360. aeolus1d/solver/hllc.py +154 -0
  361. aeolus1d/solver/hllc_eos.py +142 -0
  362. aeolus1d/solver/hllc_eos_njit.py +626 -0
  363. aeolus1d/solver/hllc_jac.py +1839 -0
  364. aeolus1d/solver/hllc_lm.py +135 -0
  365. aeolus1d/solver/implicit_euler.py +749 -0
  366. aeolus1d/solver/implicit_kernels.py +3901 -0
  367. aeolus1d/solver/implicit_network.py +1849 -0
  368. aeolus1d/solver/implicit_radau.py +396 -0
  369. aeolus1d/solver/kernels.py +645 -0
  370. aeolus1d/solver/kernels_mp5.py +491 -0
  371. aeolus1d/solver/loop_flow_steady.py +399 -0
  372. aeolus1d/solver/network_timeloop.py +3041 -0
  373. aeolus1d/solver/packed_pipes.py +342 -0
  374. aeolus1d/solver/physics_asserts.py +173 -0
  375. aeolus1d/solver/quasi_steady.py +720 -0
  376. aeolus1d/solver/quasi_steady_network.py +3356 -0
  377. aeolus1d/solver/reconstruct.py +148 -0
  378. aeolus1d/solver/rk.py +45 -0
  379. aeolus1d/solver/solver_ops.py +324 -0
  380. aeolus1d/solver/species_advection_njit.py +321 -0
  381. aeolus1d/solver/startup_banner.py +317 -0
  382. aeolus1d/solver/timeloop.py +641 -0
  383. aeolus1d/surrogates.py +123 -0
  384. aeolus1d/transmission_shift.py +171 -0
  385. aeolus1d/uq.py +149 -0
  386. aeolus1d/validation.py +778 -0
  387. aeolus1d/vehicle.py +202 -0
  388. aeolus1d-0.3.0.dist-info/METADATA +664 -0
  389. aeolus1d-0.3.0.dist-info/RECORD +454 -0
  390. aeolus1d-0.3.0.dist-info/WHEEL +4 -0
  391. aeolus1d-0.3.0.dist-info/entry_points.txt +3 -0
  392. aeolus1d-0.3.0.dist-info/licenses/AUTHORS.md +14 -0
  393. aeolus1d-0.3.0.dist-info/licenses/LICENSE +21 -0
  394. aeolus1d_gui/__init__.py +24 -0
  395. aeolus1d_gui/__main__.py +91 -0
  396. aeolus1d_gui/app.py +4043 -0
  397. aeolus1d_gui/controller/__init__.py +9 -0
  398. aeolus1d_gui/controller/mutations.py +185 -0
  399. aeolus1d_gui/editor/__init__.py +6 -0
  400. aeolus1d_gui/editor/canvas.py +2182 -0
  401. aeolus1d_gui/editor/case_renderer.py +111 -0
  402. aeolus1d_gui/editor/decorations.py +178 -0
  403. aeolus1d_gui/editor/history.py +83 -0
  404. aeolus1d_gui/editor/nodes.py +676 -0
  405. aeolus1d_gui/editor/organize_layout.py +516 -0
  406. aeolus1d_gui/editor/ports.py +53 -0
  407. aeolus1d_gui/editor/rules.py +129 -0
  408. aeolus1d_gui/editor/silhouettes.py +308 -0
  409. aeolus1d_gui/editor/visual_style.py +407 -0
  410. aeolus1d_gui/io/__init__.py +10 -0
  411. aeolus1d_gui/io/autosave.py +183 -0
  412. aeolus1d_gui/io/case_io.py +1587 -0
  413. aeolus1d_gui/io/layout_heuristic.py +287 -0
  414. aeolus1d_gui/io/layout_io.py +190 -0
  415. aeolus1d_gui/io/recent.py +69 -0
  416. aeolus1d_gui/model.py +807 -0
  417. aeolus1d_gui/post/__init__.py +5 -0
  418. aeolus1d_gui/post/cfl_dock.py +218 -0
  419. aeolus1d_gui/post/h5_reader.py +524 -0
  420. aeolus1d_gui/post/initial_conditions.py +216 -0
  421. aeolus1d_gui/post/live_plot_dock.py +192 -0
  422. aeolus1d_gui/post/modes_dock.py +336 -0
  423. aeolus1d_gui/post/network_view.py +1588 -0
  424. aeolus1d_gui/post/optimize_dock.py +737 -0
  425. aeolus1d_gui/post/plot_dock.py +1509 -0
  426. aeolus1d_gui/post/schematic_style.py +408 -0
  427. aeolus1d_gui/post/sensitivity_dock.py +280 -0
  428. aeolus1d_gui/post/solver_internals_dock.py +538 -0
  429. aeolus1d_gui/post/toml_view_dock.py +327 -0
  430. aeolus1d_gui/post/turbo_map_dock.py +262 -0
  431. aeolus1d_gui/runners/__init__.py +30 -0
  432. aeolus1d_gui/runners/qprocess_optimize_runner.py +230 -0
  433. aeolus1d_gui/runners/qprocess_runner.py +187 -0
  434. aeolus1d_gui/shell/__init__.py +1 -0
  435. aeolus1d_gui/shell/auci_compare_dialog.py +480 -0
  436. aeolus1d_gui/shell/brinkmeier_rate_widget.py +211 -0
  437. aeolus1d_gui/shell/case_settings_dock.py +132 -0
  438. aeolus1d_gui/shell/component_list.py +375 -0
  439. aeolus1d_gui/shell/doc_viewer.py +873 -0
  440. aeolus1d_gui/shell/engine_param_wizard.py +240 -0
  441. aeolus1d_gui/shell/form_factory.py +2315 -0
  442. aeolus1d_gui/shell/inspector.py +198 -0
  443. aeolus1d_gui/shell/named_rate_widget.py +169 -0
  444. aeolus1d_gui/shell/onboarding.py +452 -0
  445. aeolus1d_gui/shell/palette.py +177 -0
  446. aeolus1d_gui/shell/pid_tuning_playground.py +269 -0
  447. aeolus1d_gui/shell/play_controls.py +286 -0
  448. aeolus1d_gui/shell/runner_dock.py +1273 -0
  449. aeolus1d_gui/shell/schedule_widget.py +678 -0
  450. aeolus1d_gui/shell/settings.py +540 -0
  451. aeolus1d_gui/shell/theme.py +805 -0
  452. aeolus1d_gui/shell/time_format.py +193 -0
  453. aeolus1d_gui/shell/validators.py +35 -0
  454. aeolus1d_gui/vehicle_panel.py +642 -0
aeolus1d/__init__.py ADDED
@@ -0,0 +1,43 @@
1
+ """aeolus1d — 1D compressible gas-dynamics pipe-network solver."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ # Phase B11 — single source of truth for the package version.
8
+ # ``pyproject.toml`` carries the canonical value; this lookup
9
+ # pulls it via the installed metadata so a release bump only
10
+ # touches one file. Editable installs without metadata
11
+ # (``pip install -e .`` without a built wheel) fall back to
12
+ # the development sentinel so ``aeolus1d.__version__`` is always
13
+ # defined.
14
+ try:
15
+ from importlib.metadata import PackageNotFoundError, version
16
+
17
+ __version__ = version("aeolus1d")
18
+ except (ImportError, PackageNotFoundError): # pragma: no cover
19
+ __version__ = "0.0.0+dev"
20
+
21
+ # Public API. Re-exported lazily (PEP 562) so ``import aeolus1d`` and
22
+ # ``aeolus1d --version`` stay light — the solver / numba stack is only
23
+ # imported when one of these names is actually accessed, not on every
24
+ # ``import aeolus1d``. The canonical definitions live in ``aeolus1d.io.case``.
25
+ _LAZY_EXPORTS = frozenset({"build_network", "load_case", "run_case"})
26
+
27
+
28
+ def __getattr__(name: str) -> Any:
29
+ if name in _LAZY_EXPORTS:
30
+ from aeolus1d.io import case
31
+
32
+ return getattr(case, name)
33
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
34
+
35
+
36
+ def __dir__() -> list[str]:
37
+ return sorted(set(globals()) | _LAZY_EXPORTS)
38
+
39
+
40
+ if TYPE_CHECKING: # give type-checkers / IDEs the concrete symbols
41
+ from aeolus1d.io.case import build_network, load_case, run_case
42
+
43
+ __all__ = ["__version__", "build_network", "load_case", "run_case"]
aeolus1d/_compat.py ADDED
@@ -0,0 +1,94 @@
1
+ """Internal compatibility shims for Python version differences.
2
+
3
+ Currently provides:
4
+
5
+ * ``zip_strict`` — Python 3.9 lacks the ``strict=`` keyword on the
6
+ builtin ``zip``.
7
+ * ``trapezoid`` — NumPy 2.0 renamed ``np.trapz`` to ``np.trapezoid``;
8
+ NumPy 1.x exposes only the legacy ``trapz`` name. The shim picks
9
+ whichever is available so the project supports both NumPy
10
+ versions on both Python floors.
11
+
12
+ Drop this module when the project's ``requires-python`` floor
13
+ reaches 3.10 **and** NumPy 1.x is dropped from the dependency
14
+ matrix.
15
+
16
+ The ``zip_strict`` overload signatures mirror the typeshed
17
+ ``builtins.zip`` overloads so call-site element types are preserved
18
+ under ``mypy --strict``. The PEP 585 ``tuple[...]`` / ``Iterator[...]``
19
+ (from ``collections.abc``) generics are 3.9-safe because every
20
+ annotation here is stringified by ``from __future__ import
21
+ annotations`` — mypy resolves them, but the runtime never evaluates
22
+ them.
23
+ """
24
+ from __future__ import annotations
25
+
26
+ import sys
27
+ from collections.abc import Iterable, Iterator
28
+ from typing import Any, TypeVar, overload
29
+
30
+ import numpy as np
31
+
32
+ __all__ = ["trapezoid", "zip_strict"]
33
+
34
+
35
+ def trapezoid(y: Any, x: Any = None, **kwargs: Any) -> Any:
36
+ """Composite-trapezoidal integral — NumPy-version-portable.
37
+
38
+ NumPy 2.0 renamed ``np.trapz`` to ``np.trapezoid``. Code that
39
+ imports ``np.trapezoid`` directly fails on NumPy 1.x (no
40
+ attribute); code that uses ``np.trapz`` fails on NumPy 2.x
41
+ (DeprecationWarning → AttributeError in 2.1+). This shim
42
+ dispatches to whichever name the active NumPy exposes.
43
+ """
44
+ fn = getattr(np, "trapezoid", None) or np.trapz
45
+ if x is None:
46
+ return fn(y, **kwargs)
47
+ return fn(y, x, **kwargs)
48
+
49
+ _T1 = TypeVar("_T1")
50
+ _T2 = TypeVar("_T2")
51
+ _T3 = TypeVar("_T3")
52
+ _T4 = TypeVar("_T4")
53
+ _T5 = TypeVar("_T5")
54
+
55
+
56
+ @overload
57
+ def zip_strict(__i1: Iterable[_T1]) -> Iterator[tuple[_T1]]: ...
58
+ @overload
59
+ def zip_strict(__i1: Iterable[_T1], __i2: Iterable[_T2]) -> Iterator[tuple[_T1, _T2]]: ...
60
+ @overload
61
+ def zip_strict(
62
+ __i1: Iterable[_T1], __i2: Iterable[_T2], __i3: Iterable[_T3]
63
+ ) -> Iterator[tuple[_T1, _T2, _T3]]: ...
64
+ @overload
65
+ def zip_strict(
66
+ __i1: Iterable[_T1],
67
+ __i2: Iterable[_T2],
68
+ __i3: Iterable[_T3],
69
+ __i4: Iterable[_T4],
70
+ ) -> Iterator[tuple[_T1, _T2, _T3, _T4]]: ...
71
+ @overload
72
+ def zip_strict(
73
+ __i1: Iterable[_T1],
74
+ __i2: Iterable[_T2],
75
+ __i3: Iterable[_T3],
76
+ __i4: Iterable[_T4],
77
+ __i5: Iterable[_T5],
78
+ ) -> Iterator[tuple[_T1, _T2, _T3, _T4, _T5]]: ...
79
+
80
+
81
+ if sys.version_info >= (3, 10):
82
+ def zip_strict(*iterables): # type: ignore[no-untyped-def]
83
+ return zip(*iterables, strict=True)
84
+ else:
85
+ def zip_strict(*iterables): # type: ignore[no-untyped-def]
86
+ sentinel = object()
87
+ iters = [iter(it) for it in iterables]
88
+ while True:
89
+ row = tuple(next(it, sentinel) for it in iters)
90
+ if all(v is sentinel for v in row):
91
+ return
92
+ if any(v is sentinel for v in row):
93
+ raise ValueError("zip_strict: argument lengths differ")
94
+ yield row
@@ -0,0 +1,189 @@
1
+ """Centralised Numba kernel flags (plan 2.2 + **R2b**).
2
+
3
+ The ``@njit`` kernels read their numerically-relevant flags from this module
4
+ instead of hardcoding them, so the whole kernel set can be switched from one
5
+ place::
6
+
7
+ AEOLUS_FASTMATH=0 # disable fastmath everywhere — reproducible, slower
8
+ AEOLUS_PARALLEL=0 # compile the five prange kernels serially
9
+
10
+ ``FASTMATH`` is a constant spliced into ~191 ``@njit(...)`` decorators.
11
+ ``PARALLEL`` is consumed through the :func:`njit_parallel` decorator factory,
12
+ because the five parallel kernels do **not** share one flag set — see that
13
+ function's table. Centralising them did *not* require unifying them, which is
14
+ what had kept R2b deferred: the factory records each kernel's existing flags,
15
+ so ``chem_bs_brinkmeier`` keeps ``fastmath`` off and ``chem_numbalsoda`` keeps
16
+ ``cache`` off, both bit-preserving.
17
+
18
+ ``FASTMATH`` defaults to ``True``. Flipping it changes Numba's compiled output —
19
+ the numerically-relevant fastmath reordering (FMA contraction, reassociation).
20
+ Measured on x86_64 / numba 0.64, the two modes agree to ~3e-15 relative (pure FP
21
+ noise) but are not bit-equal, so each mode carries its own golden reference set
22
+ (``snapshots/`` vs ``snapshots/fastmath0/``).
23
+
24
+ **A fastmath build is not bit-reproducible.** This is worth stating plainly,
25
+ because it is easy to assume otherwise and the project's regression net once
26
+ claimed to verify it. Two independent effects, both measured on this codebase:
27
+
28
+ * *Host-CPU dependent.* Numba compiles for the host CPU, and fastmath lets LLVM
29
+ exploit its features, so a ``tigerlake`` build is not bit-equal to a
30
+ ``generic`` one (3 of sod's 4 fields differ).
31
+ * *Compilation-order dependent.* Even on one machine, the bits depend on which
32
+ kernels numba compiled earlier in the same process — running a network case
33
+ before a single-pipe MP5 case shifts the latter's pressure by ~1e-14.
34
+
35
+ With fastmath OFF, IEEE-strict semantics forbid both transformations and results
36
+ are bit-stable across CPU targets and compilation orders. That is why
37
+ ``tests/regression/test_golden_bit_identity.py`` — the ``assert_array_equal``
38
+ tier — runs **only** under ``AEOLUS_FASTMATH=0``, and why the default build's
39
+ end-to-end reproducibility is guarded by the measured-tolerance tier
40
+ (``rtol=1e-8``, ~6 orders above the effects above) rather than by bit-equality.
41
+ An ``assert_allclose(rtol=1e-8)`` cannot detect a 1-ULP shift, so it is the
42
+ IEEE-strict tier that catches a kernel whose numerics silently moved.
43
+
44
+ Cache isolation (audit 2026-06-10 batch C item 9)
45
+ -------------------------------------------------
46
+ Numba's on-disk cache key does **NOT** include the ``fastmath`` flag (it keys
47
+ on the source file's stat + argument types only), so two ``AEOLUS_FASTMATH``
48
+ values sharing one cache directory silently serve each other's stale
49
+ binaries — confirmed empirically on this codebase. This module therefore
50
+ routes each flag value to its own cache directory at import time (it is
51
+ imported before any kernel module defines an ``@njit`` function):
52
+
53
+ * ``AEOLUS_FASTMATH`` default (on) with no ``NUMBA_CACHE_DIR`` keeps numba's
54
+ stock per-source ``__pycache__`` caching — zero disruption, warm caches
55
+ preserved.
56
+ * ``AEOLUS_FASTMATH=0`` redirects to
57
+ ``~/.cache/aeolus1d/numba/aeolus1d-fastmath-0``.
58
+ * An existing ``NUMBA_CACHE_DIR`` is respected by appending the
59
+ ``aeolus1d-fastmath-{0|1}`` suffix under it for **both** flag values (a
60
+ user-shared cache dir is exactly the staleness hazard).
61
+
62
+ Related gotcha (same staleness family, NOT fixed by per-flag dirs): numba's
63
+ cache key also ignores ``inline="always"`` **callees** — editing an inlined
64
+ callee module serves stale callers until the cached ``*.nbc`` / ``*.nbi``
65
+ files are deleted (``find src/aeolus1d -name '*.nbc' -delete`` + same for
66
+ ``*.nbi``). Do that after editing any inline-always helper.
67
+
68
+ This is the single reviewable source for the numerically-relevant kernel flags.
69
+ ``tests/unit/test_numba_flags.py`` pins them against drift — including that no
70
+ kernel may hardcode ``fastmath=True`` or ``parallel=True`` and so escape the
71
+ switches — and the run manifest (``aeolus1d.io.manifest``) records the active
72
+ values.
73
+ """
74
+
75
+ from __future__ import annotations
76
+
77
+ import os
78
+ from typing import Any
79
+
80
+ _FALSEY = {"0", "false", "no", "off", ""}
81
+
82
+
83
+ def _env_bool(name: str, default: bool) -> bool:
84
+ raw = os.environ.get(name)
85
+ if raw is None:
86
+ return default
87
+ return raw.strip().lower() not in _FALSEY
88
+
89
+
90
+ #: Whether the @njit kernels are compiled with ``fastmath``. Default True
91
+ #: (bit-identical to the historical hardcoded value); set ``AEOLUS_FASTMATH=0``
92
+ #: for IEEE-strict, cross-environment-reproducible numerics.
93
+ FASTMATH: bool = _env_bool("AEOLUS_FASTMATH", True)
94
+
95
+ #: Whether the five ``prange`` kernels compile with numba's parallel pass.
96
+ #: Default True (bit-identical to the historical hardcoded ``parallel=True``);
97
+ #: set ``AEOLUS_PARALLEL=0`` to compile them serially. Useful when a
98
+ #: parallel-vs-serial discrepancy is suspected, which is not hypothetical here —
99
+ #: see ``tests/unit/test_packed_pipes.py``, where a ``parallel=True`` kernel and
100
+ #: its serial twin are cross-checked and the tolerance had to be set by what the
101
+ #: toolchain actually guarantees.
102
+ PARALLEL: bool = _env_bool("AEOLUS_PARALLEL", True)
103
+
104
+
105
+ def _apply_cache_isolation() -> str:
106
+ """Route this process's numba on-disk cache to a per-flag directory (see
107
+ the module docstring). Returns the directory applied, or ``""`` when
108
+ numba's stock per-source ``__pycache__`` caching is kept (the warm
109
+ default).
110
+
111
+ ``PARALLEL`` participates for the same reason ``FASTMATH`` does: numba's
112
+ cache key excludes it too, so a serial build and a parallel build sharing
113
+ one directory would serve each other's stale binaries. The suffix only
114
+ *grows* when the flag is non-default, so every existing warm cache path is
115
+ untouched.
116
+ """
117
+ suffix = f"aeolus1d-fastmath-{1 if FASTMATH else 0}"
118
+ if not PARALLEL:
119
+ suffix += "-noparallel"
120
+ root = os.environ.get("NUMBA_CACHE_DIR", "").strip()
121
+ if root:
122
+ cache_dir = os.path.join(root, suffix)
123
+ elif not FASTMATH or not PARALLEL:
124
+ cache_dir = os.path.join(
125
+ os.path.expanduser("~"), ".cache", "aeolus1d", "numba", suffix,
126
+ )
127
+ else:
128
+ # Both flags at their defaults, no user cache dir → numba's stock
129
+ # behaviour (per-source __pycache__) is already exclusive to
130
+ # fastmath=1 / parallel=1 binaries; leave it untouched.
131
+ return ""
132
+ from numba import config as _numba_config # deferred: heavy import
133
+
134
+ _numba_config.CACHE_DIR = cache_dir
135
+ return cache_dir
136
+
137
+
138
+ #: The numba cache-directory override applied for this process; ``""`` means
139
+ #: numba's stock per-source ``__pycache__`` caching is in effect.
140
+ NUMBA_CACHE_DIR_EFFECTIVE: str = _apply_cache_isolation()
141
+
142
+
143
+ def njit_parallel(
144
+ *,
145
+ cache: bool = True,
146
+ fastmath: bool = FASTMATH,
147
+ ) -> Any:
148
+ """``@njit`` decorator for the ``prange`` kernels, with ``parallel`` read
149
+ from :data:`PARALLEL` instead of hardcoded (plan item **R2b**).
150
+
151
+ Every kernel that used to write ``parallel=True`` inline now goes through
152
+ here, so the parallel flag is switchable and reviewable in one place —
153
+ exactly what ``FASTMATH`` already had.
154
+
155
+ **The five kernels, and why they are not uniform.** The reason R2b sat
156
+ deferred was a belief that centralising meant *unifying*, which would have
157
+ forced ``chem_bs_brinkmeier`` to newly opt into fastmath — a real behaviour
158
+ change on the stiff-chemistry solver. It does not: this factory records
159
+ each kernel's existing flags rather than imposing one set, so the table
160
+ below is a description, not a decision.
161
+
162
+ ====================================== ===== ======== ================
163
+ kernel cache fastmath note
164
+ ====================================== ===== ======== ================
165
+ ``packed_pipes._packed_residual_fo_par`` yes FASTMATH the common case
166
+ ``packed_pipes._packed_min_dt_par`` yes FASTMATH the common case
167
+ ``implicit_kernels`` (block assembly) yes FASTMATH the common case
168
+ ``chem_numbalsoda`` (GIL-free chem) NO FASTMATH cache=False: the
169
+ kernel closes over
170
+ a runtime function
171
+ address
172
+ ``chem_bs_brinkmeier`` (surface chem) yes **False** never opted into
173
+ fastmath; keeping
174
+ it off is
175
+ bit-preserving
176
+ ====================================== ===== ======== ================
177
+
178
+ Omitting ``fastmath`` and passing ``fastmath=False`` compile identically
179
+ (numba's default is False — verified), so writing Brinkmeier's value
180
+ explicitly is documentation, not a numerical change.
181
+ """
182
+ from numba import njit # deferred: heavy import
183
+
184
+ return njit(parallel=PARALLEL, cache=cache, fastmath=fastmath)
185
+
186
+
187
+ def as_dict() -> dict[str, bool]:
188
+ """The active flag set, for the run manifest / diagnostics."""
189
+ return {"fastmath": FASTMATH, "parallel": PARALLEL}
aeolus1d/_resources.py ADDED
@@ -0,0 +1,57 @@
1
+ """Locate bundled data directories (``examples/``) across layouts.
2
+
3
+ aeolus1d runs in three layouts and the bundled ``examples/`` tree sits in a
4
+ different place in each:
5
+
6
+ * **editable / source checkout** — ``<repo>/examples`` (a few parents up
7
+ from this module).
8
+ * **PyInstaller one-folder bundle** — ``<bundle>/examples`` next to the
9
+ launcher, surfaced via ``sys._MEIPASS`` (the spec copies the tree there).
10
+ * **pip wheel** — as of v0.3.0 the ``hatch_build.py`` build hook bundles the
11
+ top-level example cases into the wheel under ``aeolus1d/examples/`` (a sibling
12
+ of this module), so ``aeolus1d examples`` works from a plain ``pip install``.
13
+ A wheel built *without* that hook ships no examples and callers must still
14
+ tolerate ``None``.
15
+
16
+ This module centralises that resolution so the CLI ``examples`` command and
17
+ the GUI's example browser / guided tour all agree on where the examples are.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import sys
23
+ from pathlib import Path
24
+
25
+
26
+ def bundle_root() -> Path | None:
27
+ """The PyInstaller bundle root (``sys._MEIPASS``) when frozen, else ``None``.
28
+
29
+ PyInstaller sets ``sys.frozen`` and ``sys._MEIPASS`` at runtime; in a
30
+ normal interpreter neither is present and this returns ``None``.
31
+ """
32
+ if getattr(sys, "frozen", False):
33
+ meipass = getattr(sys, "_MEIPASS", None)
34
+ if meipass:
35
+ return Path(meipass)
36
+ return None
37
+
38
+
39
+ def examples_dir() -> Path | None:
40
+ """Return the bundled ``examples/`` directory, or ``None`` if unavailable.
41
+
42
+ Frozen bundle → ``<_MEIPASS>/examples``. Installed wheel (examples bundled
43
+ under ``aeolus1d/examples/``) or source / editable checkout → the nearest
44
+ ancestor ``examples/`` directory that contains ``*.toml`` cases — in a
45
+ wheel that ancestor is the package dir itself. A wheel built without the
46
+ bundling hook → ``None``.
47
+ """
48
+ root = bundle_root()
49
+ if root is not None:
50
+ cand = root / "examples"
51
+ return cand if cand.is_dir() else None
52
+ here = Path(__file__).resolve()
53
+ for parent in here.parents:
54
+ cand = parent / "examples"
55
+ if cand.is_dir() and any(cand.glob("*.toml")):
56
+ return cand
57
+ return None
aeolus1d/_tables.py ADDED
@@ -0,0 +1,109 @@
1
+ """Make the ascending-abscissa precondition of 1-D lookups self-enforcing.
2
+
3
+ Every linear interpolator in this project — ``np.interp``, and the faster
4
+ ``bc.transient._interp1d_scalar`` that replaces it on the hot path — requires
5
+ its abscissa to ascend, and **neither can detect a violation**. Both return a
6
+ plausible-looking number computed from the wrong bracket. There is no
7
+ exception, no NaN, and no warning; the run completes and the answer is wrong.
8
+
9
+ This has already cost one real bug: a valve cam whose table wrapped past
10
+ 360 deg was silently never opened (fixed 2026-07-31, MR !371). The lesson
11
+ recorded then was that the class generalises, and it does.
12
+
13
+ Until now the only guard lived in ``io.validate._validate_schedule_ordering``,
14
+ which covers three table kinds and **only on the TOML path** — so every
15
+ Python-API entry point was unguarded. The check belongs where the table is
16
+ consumed, not in a validator one of the two entry points happens to run.
17
+
18
+ Design notes:
19
+
20
+ * **Checked once, at table-build time.** These tables are built once and read
21
+ O(1e5-1e6) times; an O(n) check per lookup would be absurd. Every call site
22
+ here converts its sequence to an array exactly once, which is the natural
23
+ place.
24
+ * **Non-decreasing, not strictly increasing.** A repeated abscissa value is a
25
+ legitimate way to encode a step change, and ``np.interp`` handles it (it
26
+ takes the later value). Only a *decrease* is rejected.
27
+ * **Empty and single-point tables pass.** They have no ordering to violate,
28
+ and the interpolators already handle both.
29
+ * **NaN is rejected.** A NaN abscissa makes every comparison false, so it
30
+ neither ascends nor descends and would slip past a naive ``>=`` check — the
31
+ same "NaN loses every comparison" mechanism that let a bad density through
32
+ the dt guard before ADR-0011.
33
+ """
34
+
35
+ from __future__ import annotations
36
+
37
+ from collections.abc import Sequence
38
+
39
+ import numpy as np
40
+
41
+ __all__ = ["ascending_or_raise", "is_ascending"]
42
+
43
+
44
+ def is_ascending(xs: np.ndarray) -> bool:
45
+ """True when ``xs`` is non-decreasing and free of NaN."""
46
+ if xs.size == 0:
47
+ return True
48
+ if np.isnan(xs).any():
49
+ return False
50
+ if xs.size == 1:
51
+ return True
52
+ return bool(np.all(xs[1:] >= xs[:-1]))
53
+
54
+
55
+ def ascending_or_raise(
56
+ xs: Sequence[float] | np.ndarray,
57
+ *,
58
+ name: str,
59
+ where: str,
60
+ hint: str = "",
61
+ ) -> np.ndarray:
62
+ """Return ``xs`` as a float array, raising if it does not ascend.
63
+
64
+ Parameters
65
+ ----------
66
+ xs
67
+ The abscissa (the ``xp`` of an ``np.interp``-style lookup).
68
+ name
69
+ The field name as the user wrote it, e.g. ``"SoC_grid"``.
70
+ where
71
+ What owns the table, e.g. ``"BatteryECM"`` — so the message names a
72
+ thing the caller can find.
73
+ hint
74
+ Optional extra sentence for cases with a known-common cause.
75
+
76
+ Raises
77
+ ------
78
+ ValueError
79
+ If any element decreases relative to its predecessor, or any is NaN.
80
+ The message names the first offending index and both values, because
81
+ "somewhere in a 400-point table" is not actionable.
82
+ """
83
+ arr = np.asarray(xs, dtype=np.float64)
84
+ if arr.ndim != 1:
85
+ raise ValueError(
86
+ f"{where}: {name} must be one-dimensional, got shape {arr.shape}."
87
+ )
88
+ nan_at = np.flatnonzero(np.isnan(arr))
89
+ if nan_at.size:
90
+ raise ValueError(
91
+ f"{where}: {name}[{int(nan_at[0])}] is NaN. A NaN abscissa loses "
92
+ f"every comparison, so the interpolator cannot bracket a query "
93
+ f"point and returns a wrong value silently."
94
+ )
95
+ if arr.size < 2:
96
+ return arr
97
+ bad = np.flatnonzero(arr[1:] < arr[:-1])
98
+ if bad.size:
99
+ i = int(bad[0]) + 1
100
+ tail = f" {hint}" if hint else ""
101
+ raise ValueError(
102
+ f"{where}: {name} must be non-decreasing, but {name}[{i}] = "
103
+ f"{arr[i]:g} < {name}[{i - 1}] = {arr[i - 1]:g}. Linear "
104
+ f"interpolation assumes an ascending abscissa and returns "
105
+ f"silently wrong values otherwise — it cannot detect this "
106
+ f"itself, so the run would complete with a plausible but "
107
+ f"incorrect answer.{tail}"
108
+ )
109
+ return arr
aeolus1d/_types.py ADDED
@@ -0,0 +1,103 @@
1
+ """Small shared enums and integer codes used by Numba kernels.
2
+
3
+ These map Python-side names to integer codes so that @njit kernels can branch on
4
+ them cheaply without calling back into Python.
5
+ """
6
+
7
+ from enum import IntEnum
8
+
9
+
10
+ class End(IntEnum):
11
+ LEFT = 0
12
+ RIGHT = 1
13
+
14
+
15
+ class Limiter(IntEnum):
16
+ NONE = 0
17
+ MINMOD = 1
18
+ VAN_LEER = 2
19
+
20
+
21
+ # ``[case] limiter`` name -> code. Lives here, beside the enum, because BOTH
22
+ # builders need it and they cannot import from each other: ``io.case`` imports
23
+ # ``io.build``, so the mapping cannot live in ``io.case`` where it started.
24
+ # Keep in sync with the ``limiter`` Literal in ``io.schema.CaseHeader``.
25
+ LIMITER_BY_NAME: dict[str, Limiter] = {
26
+ "minmod": Limiter.MINMOD,
27
+ "van_leer": Limiter.VAN_LEER,
28
+ "none": Limiter.NONE,
29
+ }
30
+
31
+
32
+ class RKScheme(IntEnum):
33
+ SSP_RK2 = 2
34
+ SSP_RK3 = 3
35
+
36
+
37
+ class BCKind(IntEnum):
38
+ WALL = 0
39
+ STAGNATION_INLET = 1
40
+ PRESSURE_OUTLET = 2
41
+ NONREFLECTING = 3
42
+ # (A JUNCTION_PORT kind was reserved in milestone 1 but never used —
43
+ # ports are resolved inside aeolus1d.components.junction instead.)
44
+
45
+
46
+ # --------------------------------------------------------------------------
47
+ # Closure-kind enums.
48
+ #
49
+ # These mirror the integer dispatch codes registered in
50
+ # ``aeolus1d.closures.registry`` (the single source of truth — see
51
+ # ``register_closure``). Each member's *name* (lower-cased) must equal a
52
+ # registered closure name and its *value* must equal that kind's dispatch code.
53
+ # ``tests/unit/test_closure_registry.py`` locks this with a drift test, so a new
54
+ # kind added to the registry without a matching member here fails loudly. They
55
+ # stay hand-written (not generated) because static type-checkers and ``.value``
56
+ # call sites need the members to exist statically — the same reason the schema
57
+ # ``Literal[...]`` arms can't be registry-generated either.
58
+ # --------------------------------------------------------------------------
59
+
60
+
61
+ class FrictionKind(IntEnum):
62
+ NONE = 0
63
+ DARCY_CONST = 1
64
+ COLEBROOK = 2
65
+ MINOR_LOSS = 3
66
+ # PYTHON is provenance only: the @njit kernels never dispatch on it.
67
+ # ``_py_closure_kinds`` substitutes NONE whenever ``pipe.py_friction``
68
+ # is set, so the compiled side does flux+area and the Python source is
69
+ # overlaid afterwards. The code exists so a snapshot / GUI round-trip
70
+ # can still say WHICH closure the pipe carries.
71
+ PYTHON = 4
72
+
73
+
74
+ class HeatKind(IntEnum):
75
+ NONE = 0
76
+ CONSTANT_TWALL = 1
77
+ EGR_COOLER = 2
78
+ CHARGE_AIR_COOLER = 3
79
+ FLOW_NU = 4
80
+
81
+
82
+ class PorousKind(IntEnum):
83
+ NONE = 0
84
+ DARCY_FORCHHEIMER = 1
85
+ DARCY_FORCHHEIMER_KC = 2
86
+ # Provenance only, like FrictionKind.PYTHON — the @njit kernels never
87
+ # dispatch on it (``_py_closure_kinds`` substitutes NONE whenever
88
+ # ``pipe.py_porous`` is set).
89
+ PYTHON = 3
90
+
91
+
92
+ class CatalystKind(IntEnum):
93
+ NONE = 0
94
+ TWO_PHASE_HEAT = 1
95
+ TWO_PHASE_HEAT_VAR_NU = 2
96
+ MECHANISM = 3
97
+
98
+
99
+ class EOSKind(IntEnum):
100
+ IDEAL = 0
101
+ REDLICH_KWONG = 1
102
+ PENG_ROBINSON = 2
103
+ TABULATED = 3
@@ -0,0 +1,61 @@
1
+ """Analysis tools — case inspection and post-processing.
2
+
3
+ * :py:mod:`aeolus1d.analysis.cfl_inspector` — pre-run CFL /
4
+ max-wave-speed analysis, identifies which pipe + cell will
5
+ limit the timeloop's dt.
6
+ * :py:mod:`aeolus1d.analysis.engine_metrics` — Phase 7g/7c per-cycle
7
+ indicated engine performance: IMEP, ISFC, η_i, p–V trace;
8
+ multi-cylinder fleet aggregation via MultiCycleAccumulator.
9
+ * :py:mod:`aeolus1d.analysis.acoustics` — Phase 7g.2 frequency-
10
+ domain post-processing: SPL / dB(A), power spectrum, 1/1 and
11
+ 1/3 octave-band reduction, engine-order analysis,
12
+ transmission / insertion loss for muffler design.
13
+ * :py:mod:`aeolus1d.analysis.system_reports` — Phase 7g.2
14
+ system-level reports: turbo operating-point overlay on the
15
+ compressor map (with surge / choke envelope), per-cycle Sankey-
16
+ ready energy balance, cumulative emissions integration past a
17
+ probe, drive-cycle (NEDC / WLTP / FTP) integration harness,
18
+ steady-state / periodic-limit-cycle convergence detection.
19
+ * :py:mod:`aeolus1d.analysis.linearize` — Phase L1 state-space
20
+ linearization ``(A, B, C, D)`` around a steady operating point,
21
+ for control synthesis / Bode / stability analysis in scipy or
22
+ python-control.
23
+ """
24
+
25
+ from aeolus1d.analysis.engine_metrics import (
26
+ CycleAccumulator,
27
+ EngineMetrics,
28
+ FMEPCoeffs,
29
+ MultiCycleAccumulator,
30
+ cycle_summary,
31
+ )
32
+ from aeolus1d.analysis.linearize import (
33
+ InputHandle,
34
+ OutputProbe,
35
+ StateSpace,
36
+ linearize_single_pipe,
37
+ )
38
+ from aeolus1d.analysis.nox_post import (
39
+ equilibrium_NO_ppm,
40
+ estimate_engine_out_NOx_ppm,
41
+ integrate_zeldovich_nox,
42
+ lean_burnt_composition,
43
+ mass_fraction_NO_to_ppm,
44
+ )
45
+
46
+ __all__ = [
47
+ "CycleAccumulator",
48
+ "EngineMetrics",
49
+ "FMEPCoeffs",
50
+ "InputHandle",
51
+ "MultiCycleAccumulator",
52
+ "OutputProbe",
53
+ "StateSpace",
54
+ "cycle_summary",
55
+ "equilibrium_NO_ppm",
56
+ "estimate_engine_out_NOx_ppm",
57
+ "integrate_zeldovich_nox",
58
+ "lean_burnt_composition",
59
+ "linearize_single_pipe",
60
+ "mass_fraction_NO_to_ppm",
61
+ ]