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.
- aeolus1d/__init__.py +43 -0
- aeolus1d/_compat.py +94 -0
- aeolus1d/_numba_flags.py +189 -0
- aeolus1d/_resources.py +57 -0
- aeolus1d/_tables.py +109 -0
- aeolus1d/_types.py +103 -0
- aeolus1d/analysis/__init__.py +61 -0
- aeolus1d/analysis/acoustics.py +879 -0
- aeolus1d/analysis/acoustics_tm.py +172 -0
- aeolus1d/analysis/cfl_inspector.py +406 -0
- aeolus1d/analysis/core_zone_probe.py +536 -0
- aeolus1d/analysis/energy_audit.py +1123 -0
- aeolus1d/analysis/engine_metrics.py +626 -0
- aeolus1d/analysis/linearize.py +1364 -0
- aeolus1d/analysis/nox_post.py +457 -0
- aeolus1d/analysis/random_cycle.py +189 -0
- aeolus1d/analysis/sensitivities.py +199 -0
- aeolus1d/analysis/system_reports.py +433 -0
- aeolus1d/analysis/turbo_map_viz.py +367 -0
- aeolus1d/analysis/vehicle_tasks.py +200 -0
- aeolus1d/audio.py +286 -0
- aeolus1d/battery_ecm.py +164 -0
- aeolus1d/bc/__init__.py +103 -0
- aeolus1d/bc/base.py +197 -0
- aeolus1d/bc/egr_valve.py +211 -0
- aeolus1d/bc/mass_flow_inlet.py +134 -0
- aeolus1d/bc/nonreflecting.py +96 -0
- aeolus1d/bc/orifice_flow.py +209 -0
- aeolus1d/bc/pressure_outlet.py +103 -0
- aeolus1d/bc/stagnation_inlet.py +145 -0
- aeolus1d/bc/throttle.py +171 -0
- aeolus1d/bc/transient.py +272 -0
- aeolus1d/bc/valve.py +151 -0
- aeolus1d/bc/valve_orifice.py +230 -0
- aeolus1d/bc/velocity_inlet.py +96 -0
- aeolus1d/bench/__init__.py +1 -0
- aeolus1d/bench/abgrall_interface.py +156 -0
- aeolus1d/bench/colebrook.py +89 -0
- aeolus1d/bench/helmholtz.py +148 -0
- aeolus1d/bench/helmholtz_quasi2d.py +161 -0
- aeolus1d/bench/helmholtz_resonator.py +108 -0
- aeolus1d/bench/lax.py +72 -0
- aeolus1d/bench/moody.py +94 -0
- aeolus1d/bench/nozzle.py +154 -0
- aeolus1d/bench/porous_plug.py +106 -0
- aeolus1d/bench/riemann_suite.py +255 -0
- aeolus1d/bench/shock_reflection.py +115 -0
- aeolus1d/bench/shu_osher.py +123 -0
- aeolus1d/bench/smooth_advection.py +257 -0
- aeolus1d/bench/sod.py +185 -0
- aeolus1d/bench/tjunction.py +111 -0
- aeolus1d/bench/tjunction_cp.py +89 -0
- aeolus1d/bench/woodward_colella.py +98 -0
- aeolus1d/chemistry/__init__.py +19 -0
- aeolus1d/chemistry/burcat_db.py +214 -0
- aeolus1d/chemistry/db.py +309 -0
- aeolus1d/chemistry/db_data/CH4.npz +0 -0
- aeolus1d/chemistry/db_data/CO.npz +0 -0
- aeolus1d/chemistry/db_data/CO2.npz +0 -0
- aeolus1d/chemistry/db_data/H.npz +0 -0
- aeolus1d/chemistry/db_data/H2.npz +0 -0
- aeolus1d/chemistry/db_data/H2O.npz +0 -0
- aeolus1d/chemistry/db_data/H2O2.npz +0 -0
- aeolus1d/chemistry/db_data/HO2.npz +0 -0
- aeolus1d/chemistry/db_data/N2.npz +0 -0
- aeolus1d/chemistry/db_data/N2_janaf_sample.dat +21 -0
- aeolus1d/chemistry/db_data/NH3.npz +0 -0
- aeolus1d/chemistry/db_data/NO.npz +0 -0
- aeolus1d/chemistry/db_data/O.npz +0 -0
- aeolus1d/chemistry/db_data/O2.npz +0 -0
- aeolus1d/chemistry/db_data/OH.npz +0 -0
- aeolus1d/chemistry/equilibrium.py +211 -0
- aeolus1d/chemistry/fuel_stoichiometry.py +157 -0
- aeolus1d/chemistry/importers/__init__.py +15 -0
- aeolus1d/chemistry/importers/burcat.py +230 -0
- aeolus1d/chemistry/importers/cantera_yaml.py +943 -0
- aeolus1d/chemistry/importers/chemkin_mech.py +828 -0
- aeolus1d/chemistry/importers/janaf.py +229 -0
- aeolus1d/chemistry/importers/nasa7.py +317 -0
- aeolus1d/chemistry/kinetics.py +1049 -0
- aeolus1d/chemistry/kinetics_njit.py +615 -0
- aeolus1d/chemistry/mech_db/ammonia_scr_demo.json +71 -0
- aeolus1d/chemistry/mech_db/h2_oxidation_demo.json +168 -0
- aeolus1d/chemistry/mech_db.py +221 -0
- aeolus1d/chemistry/mixture.py +725 -0
- aeolus1d/chemistry/reaction.py +276 -0
- aeolus1d/chemistry/species.py +180 -0
- aeolus1d/chemistry/surface_kinetics.py +252 -0
- aeolus1d/cli/__init__.py +1 -0
- aeolus1d/cli/_shared.py +29 -0
- aeolus1d/cli/commands/__init__.py +61 -0
- aeolus1d/cli/commands/acoustics.py +137 -0
- aeolus1d/cli/commands/analyze_cfl.py +44 -0
- aeolus1d/cli/commands/auci_compare.py +54 -0
- aeolus1d/cli/commands/audio.py +83 -0
- aeolus1d/cli/commands/bench.py +59 -0
- aeolus1d/cli/commands/cycle_settle.py +215 -0
- aeolus1d/cli/commands/drive_cycle.py +130 -0
- aeolus1d/cli/commands/examples.py +75 -0
- aeolus1d/cli/commands/expand.py +74 -0
- aeolus1d/cli/commands/export.py +36 -0
- aeolus1d/cli/commands/export_fmu.py +31 -0
- aeolus1d/cli/commands/extract.py +50 -0
- aeolus1d/cli/commands/gui.py +66 -0
- aeolus1d/cli/commands/import_.py +47 -0
- aeolus1d/cli/commands/inspect_gtm.py +35 -0
- aeolus1d/cli/commands/modes.py +174 -0
- aeolus1d/cli/commands/optimize.py +88 -0
- aeolus1d/cli/commands/parallel.py +63 -0
- aeolus1d/cli/commands/run.py +967 -0
- aeolus1d/cli/commands/sensitivity.py +116 -0
- aeolus1d/cli/commands/thermo_db.py +76 -0
- aeolus1d/cli/commands/turbo_map.py +99 -0
- aeolus1d/cli/commands/warmup.py +154 -0
- aeolus1d/cli/doctor.py +242 -0
- aeolus1d/cli/main.py +127 -0
- aeolus1d/cli/registry.py +102 -0
- aeolus1d/closures/__init__.py +5 -0
- aeolus1d/closures/_extras_layout.py +81 -0
- aeolus1d/closures/bend_loss.py +86 -0
- aeolus1d/closures/catalyst.py +705 -0
- aeolus1d/closures/catalyst_eos.py +212 -0
- aeolus1d/closures/chem_linop.py +111 -0
- aeolus1d/closures/chem_surrogate.py +97 -0
- aeolus1d/closures/diesel_predictive_combustion.py +214 -0
- aeolus1d/closures/doc_sulfur.py +246 -0
- aeolus1d/closures/eos.py +529 -0
- aeolus1d/closures/eos_njit.py +791 -0
- aeolus1d/closures/eos_tabulated.py +275 -0
- aeolus1d/closures/eos_tabulated_njit.py +636 -0
- aeolus1d/closures/film_transfer.py +106 -0
- aeolus1d/closures/filter.py +1357 -0
- aeolus1d/closures/flame_geometry.py +92 -0
- aeolus1d/closures/friction.py +360 -0
- aeolus1d/closures/gas.py +63 -0
- aeolus1d/closures/hc_oxidation.py +300 -0
- aeolus1d/closures/heat.py +370 -0
- aeolus1d/closures/hnco_hydrolysis.py +136 -0
- aeolus1d/closures/incylinder_turbulence.py +65 -0
- aeolus1d/closures/isat_chem.py +141 -0
- aeolus1d/closures/laminar_flame_speed.py +101 -0
- aeolus1d/closures/learned_junction_bc.py +292 -0
- aeolus1d/closures/learned_models/junction_bc_resistive.npz +0 -0
- aeolus1d/closures/learned_models/turbo_map_centrifugal.npz +0 -0
- aeolus1d/closures/learned_turbo_map.py +378 -0
- aeolus1d/closures/mechanism.py +524 -0
- aeolus1d/closures/monolith.py +173 -0
- aeolus1d/closures/multi_stage_wiebe.py +404 -0
- aeolus1d/closures/pinn.py +211 -0
- aeolus1d/closures/pollutants.py +549 -0
- aeolus1d/closures/porous.py +509 -0
- aeolus1d/closures/reaction_demo.py +232 -0
- aeolus1d/closures/reaction_demo_njit.py +35 -0
- aeolus1d/closures/registry.py +198 -0
- aeolus1d/closures/scr_kinetics.py +449 -0
- aeolus1d/closures/scr_storage.py +388 -0
- aeolus1d/closures/si_turbulent_combustion.py +161 -0
- aeolus1d/closures/species_contract.py +186 -0
- aeolus1d/closures/thiele.py +225 -0
- aeolus1d/closures/transport.py +136 -0
- aeolus1d/closures/twc.py +433 -0
- aeolus1d/closures/twc_brinkmeier.py +1237 -0
- aeolus1d/closures/urea_decomposition.py +177 -0
- aeolus1d/closures/user_code.py +359 -0
- aeolus1d/closures/wall_coolant.py +165 -0
- aeolus1d/closures/wall_thermal.py +411 -0
- aeolus1d/components/__init__.py +90 -0
- aeolus1d/components/coolant_pipe.py +207 -0
- aeolus1d/components/cylinder.py +3374 -0
- aeolus1d/components/helmholtz_resonator.py +231 -0
- aeolus1d/components/junction.py +1219 -0
- aeolus1d/components/junction_cp.py +519 -0
- aeolus1d/components/junction_ehr.py +308 -0
- aeolus1d/components/junction_passthrough.py +276 -0
- aeolus1d/components/pipe.py +1024 -0
- aeolus1d/components/protocols.py +124 -0
- aeolus1d/components/species_coupling.py +390 -0
- aeolus1d/components/turbo.py +1832 -0
- aeolus1d/components/turbo_csv.py +185 -0
- aeolus1d/components/turbo_data/example_centrifugal.csv +37 -0
- aeolus1d/components/turbo_data/example_centrifugal.npz +0 -0
- aeolus1d/components/valve_flow.py +187 -0
- aeolus1d/components/volume.py +415 -0
- aeolus1d/constants.py +9 -0
- aeolus1d/control/__init__.py +79 -0
- aeolus1d/control/boost_controller.py +89 -0
- aeolus1d/control/dpf_heater_controller.py +100 -0
- aeolus1d/control/egr_valve_controller.py +138 -0
- aeolus1d/control/epw_closed_loop.py +282 -0
- aeolus1d/control/fuel_injector_controller.py +176 -0
- aeolus1d/control/pid_tune.py +228 -0
- aeolus1d/control/signal_toolbox.py +327 -0
- aeolus1d/control/spark_timing_controller.py +159 -0
- aeolus1d/control/step_response.py +181 -0
- aeolus1d/control/wastegate_controller.py +127 -0
- aeolus1d/coolant.py +147 -0
- aeolus1d/doe.py +223 -0
- aeolus1d/drive_cycles.py +208 -0
- aeolus1d/driveline.py +360 -0
- aeolus1d/driver.py +200 -0
- aeolus1d/emotor.py +179 -0
- aeolus1d/energy_management.py +158 -0
- aeolus1d/engine_oil.py +147 -0
- aeolus1d/engine_post.py +492 -0
- aeolus1d/examples/01_single_pipe_sod.toml +41 -0
- aeolus1d/examples/02_cd_nozzle.toml +43 -0
- aeolus1d/examples/03_two_pipes_junction.toml +67 -0
- aeolus1d/examples/04_pulsed_intake.toml +41 -0
- aeolus1d/examples/05_catalyst_plug.toml +50 -0
- aeolus1d/examples/06_helmholtz_quasi2d.toml +57 -0
- aeolus1d/examples/07_catalyst_thermal_front.toml +58 -0
- aeolus1d/examples/08_reactive_pipe.toml +49 -0
- aeolus1d/examples/09_real_gas_shock_tube.toml +58 -0
- aeolus1d/examples/10_cp_junction_kloss.toml +82 -0
- aeolus1d/examples/11_neck_pipe_junction.toml +87 -0
- aeolus1d/examples/12_cp_junction.toml +81 -0
- aeolus1d/examples/13_ehr_helmholtz.toml +45 -0
- aeolus1d/examples/14_py_junction_bc.toml +48 -0
- aeolus1d/examples/15_strang_chemistry.toml +55 -0
- aeolus1d/examples/16_bs_chemistry.toml +56 -0
- aeolus1d/examples/17_real_gas_nozzle.toml +60 -0
- aeolus1d/examples/18_real_gas_catalyst.toml +65 -0
- aeolus1d/examples/19_real_gas_plenum.toml +80 -0
- aeolus1d/examples/20_multi_species.toml +53 -0
- aeolus1d/examples/21_multi_species_kinetics.toml +56 -0
- aeolus1d/examples/22_lodi_port.toml +55 -0
- aeolus1d/examples/23_mp5_sod.toml +69 -0
- aeolus1d/examples/24_hllc_lm_pulse.toml +61 -0
- aeolus1d/examples/25_valve_inlet.toml +55 -0
- aeolus1d/examples/26_valve_orifice.toml +54 -0
- aeolus1d/examples/27_compressor.toml +71 -0
- aeolus1d/examples/28_turbo_shaft.toml +126 -0
- aeolus1d/examples/29_learned_junction_bc.toml +83 -0
- aeolus1d/examples/30_learned_turbo_map.toml +74 -0
- aeolus1d/examples/31_cylinder.toml +104 -0
- aeolus1d/examples/35_mechanism_pipe.toml +56 -0
- aeolus1d/examples/38_engine_turbo_scr.toml +238 -0
- aeolus1d/examples/39_4cyl_engine_turbo_scr.toml +669 -0
- aeolus1d/examples/40_implicit_adaptive_pipe.toml +64 -0
- aeolus1d/examples/41_dpf_regen.toml +111 -0
- aeolus1d/examples/42_scr_urea_injector.toml +172 -0
- aeolus1d/examples/43_scrf.toml +217 -0
- aeolus1d/examples/44_egr_cooler.toml +56 -0
- aeolus1d/examples/45_two_stage_scr.toml +212 -0
- aeolus1d/examples/46_doc.toml +124 -0
- aeolus1d/examples/48_hnco_hydrolysis.toml +124 -0
- aeolus1d/examples/49_dpf_no_inhibition.toml +101 -0
- aeolus1d/examples/50_dpf_active_regen.toml +107 -0
- aeolus1d/examples/50_two_cyl_engine.toml +279 -0
- aeolus1d/examples/51_vgt_boost_ctrl.toml +135 -0
- aeolus1d/examples/52_knock_event.toml +127 -0
- aeolus1d/examples/53_dpf_heater_ctrl.toml +117 -0
- aeolus1d/examples/54_adaptive_picard_turbo.toml +159 -0
- aeolus1d/examples/55_dpf_regen_hrr.toml +100 -0
- aeolus1d/examples/55_vgt_3d_turbo.toml +154 -0
- aeolus1d/examples/56_dpf_regen_lowmach.toml +104 -0
- aeolus1d/examples/57_picard_stiffness_step.toml +189 -0
- aeolus1d/examples/58_tabulated_eos_implicit.toml +73 -0
- aeolus1d/examples/59_wastegate_boost_ctrl.toml +148 -0
- aeolus1d/examples/60_twin_sequential_turbo.toml +235 -0
- aeolus1d/examples/61_doc_sulfur_poisoning.toml +167 -0
- aeolus1d/examples/62_egr_valve_ctrl.toml +98 -0
- aeolus1d/examples/63_spark_timing_ctrl.toml +139 -0
- aeolus1d/examples/71_ktm_510_si.toml +256 -0
- aeolus1d/examples/72_throttle_demo.toml +76 -0
- aeolus1d/examples/74_twc.toml +226 -0
- aeolus1d/examples/75_coolant_pipe.toml +67 -0
- aeolus1d/examples/76_wltp_lite_dpf.toml +194 -0
- aeolus1d/examples/77_pipe_flow_heat.toml +57 -0
- aeolus1d/examples/78_pipe_bend_loss.toml +53 -0
- aeolus1d/examples/79_orifice_plate.toml +57 -0
- aeolus1d/examples/80_pipe_wall_warmup.toml +60 -0
- aeolus1d/examples/81_bend_geometric.toml +50 -0
- aeolus1d/examples/82_multilayer_wall.toml +72 -0
- aeolus1d/examples/83_turbo_robustness.toml +145 -0
- aeolus1d/examples/84_dpf_substrate_inertia.toml +80 -0
- aeolus1d/examples/85_vehicle_drive_cycle.toml +65 -0
- aeolus1d/examples/86_electrified_drive_cycle.toml +56 -0
- aeolus1d/examples/87_multicomponent_variable_gamma_shock_tube.toml +69 -0
- aeolus1d/examples/88_dd13_diesel.toml +115 -0
- aeolus1d/examples/89_epw_diesel.toml +1025 -0
- aeolus1d/examples/90_variable_cv_real_gas.toml +72 -0
- aeolus1d/examples/91_cylinder_species_transport.toml +232 -0
- aeolus1d/fmi.py +325 -0
- aeolus1d/fuel_map.py +80 -0
- aeolus1d/fuels.py +107 -0
- aeolus1d/homotopy.py +216 -0
- aeolus1d/hybrid.py +313 -0
- aeolus1d/io/__init__.py +47 -0
- aeolus1d/io/_ignition_mode.py +104 -0
- aeolus1d/io/_kind_codec.py +286 -0
- aeolus1d/io/build.py +3451 -0
- aeolus1d/io/case.py +939 -0
- aeolus1d/io/case_upgrades.py +227 -0
- aeolus1d/io/cgns_writer.py +296 -0
- aeolus1d/io/expand_case.py +302 -0
- aeolus1d/io/interop/__init__.py +197 -0
- aeolus1d/io/interop/_xml_common.py +151 -0
- aeolus1d/io/interop/avl_boost.py +140 -0
- aeolus1d/io/interop/avl_cruise_m/__init__.py +107 -0
- aeolus1d/io/interop/avl_cruise_m/_calibration.py +1405 -0
- aeolus1d/io/interop/avl_cruise_m/_core.py +3937 -0
- aeolus1d/io/interop/avl_cruise_m/_geometry.py +683 -0
- aeolus1d/io/interop/avl_cruise_m/_porous.py +601 -0
- aeolus1d/io/interop/avl_cruise_m/_urea.py +467 -0
- aeolus1d/io/interop/avl_cruise_m/types.py +140 -0
- aeolus1d/io/interop/avl_cruise_m/units.py +212 -0
- aeolus1d/io/interop/avl_cruise_m_tune.py +1004 -0
- aeolus1d/io/interop/btc_turbo_map.py +767 -0
- aeolus1d/io/interop/extract.py +247 -0
- aeolus1d/io/interop/fmu.py +440 -0
- aeolus1d/io/interop/gt_maps.py +393 -0
- aeolus1d/io/interop/gt_params.py +207 -0
- aeolus1d/io/interop/gt_power.py +171 -0
- aeolus1d/io/interop/gt_suite_native.py +2319 -0
- aeolus1d/io/interop/openwam.py +336 -0
- aeolus1d/io/interop/schematic.py +795 -0
- aeolus1d/io/interop/species_db.py +237 -0
- aeolus1d/io/interop/ucp.py +434 -0
- aeolus1d/io/interop/wave.py +145 -0
- aeolus1d/io/manifest.py +152 -0
- aeolus1d/io/parsers.py +1819 -0
- aeolus1d/io/resume.py +749 -0
- aeolus1d/io/schema.py +3468 -0
- aeolus1d/io/schema_help.py +2636 -0
- aeolus1d/io/schema_upgrades.py +166 -0
- aeolus1d/io/snapshot.py +399 -0
- aeolus1d/io/source_pos.py +202 -0
- aeolus1d/io/tracer.py +509 -0
- aeolus1d/io/validate.py +2443 -0
- aeolus1d/io/vehicle_case.py +566 -0
- aeolus1d/io/writer.py +552 -0
- aeolus1d/io/xdmf.py +309 -0
- aeolus1d/logging.py +193 -0
- aeolus1d/modes/__init__.py +65 -0
- aeolus1d/modes/assemble.py +130 -0
- aeolus1d/modes/elements.py +168 -0
- aeolus1d/modes/network.py +371 -0
- aeolus1d/modes/solve.py +125 -0
- aeolus1d/notebook.py +182 -0
- aeolus1d/optimize.py +811 -0
- aeolus1d/parallel.py +139 -0
- aeolus1d/planetary.py +71 -0
- aeolus1d/py.typed +0 -0
- aeolus1d/regen.py +58 -0
- aeolus1d/runtime/__init__.py +8 -0
- aeolus1d/runtime/cfl_ramp.py +84 -0
- aeolus1d/runtime/live_control.py +270 -0
- aeolus1d/scaffold/__init__.py +20 -0
- aeolus1d/scaffold/engine.py +302 -0
- aeolus1d/scaffold/thermal_network.py +169 -0
- aeolus1d/scaffold/turbo_sizing.py +253 -0
- aeolus1d/scaffold/wizards.py +175 -0
- aeolus1d/sensitivity.py +158 -0
- aeolus1d/solver/__init__.py +1 -0
- aeolus1d/solver/chem_bs_brinkmeier.py +946 -0
- aeolus1d/solver/chem_bs_jit.py +366 -0
- aeolus1d/solver/chem_diagnostics.py +252 -0
- aeolus1d/solver/chem_numbalsoda.py +360 -0
- aeolus1d/solver/hllc.py +154 -0
- aeolus1d/solver/hllc_eos.py +142 -0
- aeolus1d/solver/hllc_eos_njit.py +626 -0
- aeolus1d/solver/hllc_jac.py +1839 -0
- aeolus1d/solver/hllc_lm.py +135 -0
- aeolus1d/solver/implicit_euler.py +749 -0
- aeolus1d/solver/implicit_kernels.py +3901 -0
- aeolus1d/solver/implicit_network.py +1849 -0
- aeolus1d/solver/implicit_radau.py +396 -0
- aeolus1d/solver/kernels.py +645 -0
- aeolus1d/solver/kernels_mp5.py +491 -0
- aeolus1d/solver/loop_flow_steady.py +399 -0
- aeolus1d/solver/network_timeloop.py +3041 -0
- aeolus1d/solver/packed_pipes.py +342 -0
- aeolus1d/solver/physics_asserts.py +173 -0
- aeolus1d/solver/quasi_steady.py +720 -0
- aeolus1d/solver/quasi_steady_network.py +3356 -0
- aeolus1d/solver/reconstruct.py +148 -0
- aeolus1d/solver/rk.py +45 -0
- aeolus1d/solver/solver_ops.py +324 -0
- aeolus1d/solver/species_advection_njit.py +321 -0
- aeolus1d/solver/startup_banner.py +317 -0
- aeolus1d/solver/timeloop.py +641 -0
- aeolus1d/surrogates.py +123 -0
- aeolus1d/transmission_shift.py +171 -0
- aeolus1d/uq.py +149 -0
- aeolus1d/validation.py +778 -0
- aeolus1d/vehicle.py +202 -0
- aeolus1d-0.3.0.dist-info/METADATA +664 -0
- aeolus1d-0.3.0.dist-info/RECORD +454 -0
- aeolus1d-0.3.0.dist-info/WHEEL +4 -0
- aeolus1d-0.3.0.dist-info/entry_points.txt +3 -0
- aeolus1d-0.3.0.dist-info/licenses/AUTHORS.md +14 -0
- aeolus1d-0.3.0.dist-info/licenses/LICENSE +21 -0
- aeolus1d_gui/__init__.py +24 -0
- aeolus1d_gui/__main__.py +91 -0
- aeolus1d_gui/app.py +4043 -0
- aeolus1d_gui/controller/__init__.py +9 -0
- aeolus1d_gui/controller/mutations.py +185 -0
- aeolus1d_gui/editor/__init__.py +6 -0
- aeolus1d_gui/editor/canvas.py +2182 -0
- aeolus1d_gui/editor/case_renderer.py +111 -0
- aeolus1d_gui/editor/decorations.py +178 -0
- aeolus1d_gui/editor/history.py +83 -0
- aeolus1d_gui/editor/nodes.py +676 -0
- aeolus1d_gui/editor/organize_layout.py +516 -0
- aeolus1d_gui/editor/ports.py +53 -0
- aeolus1d_gui/editor/rules.py +129 -0
- aeolus1d_gui/editor/silhouettes.py +308 -0
- aeolus1d_gui/editor/visual_style.py +407 -0
- aeolus1d_gui/io/__init__.py +10 -0
- aeolus1d_gui/io/autosave.py +183 -0
- aeolus1d_gui/io/case_io.py +1587 -0
- aeolus1d_gui/io/layout_heuristic.py +287 -0
- aeolus1d_gui/io/layout_io.py +190 -0
- aeolus1d_gui/io/recent.py +69 -0
- aeolus1d_gui/model.py +807 -0
- aeolus1d_gui/post/__init__.py +5 -0
- aeolus1d_gui/post/cfl_dock.py +218 -0
- aeolus1d_gui/post/h5_reader.py +524 -0
- aeolus1d_gui/post/initial_conditions.py +216 -0
- aeolus1d_gui/post/live_plot_dock.py +192 -0
- aeolus1d_gui/post/modes_dock.py +336 -0
- aeolus1d_gui/post/network_view.py +1588 -0
- aeolus1d_gui/post/optimize_dock.py +737 -0
- aeolus1d_gui/post/plot_dock.py +1509 -0
- aeolus1d_gui/post/schematic_style.py +408 -0
- aeolus1d_gui/post/sensitivity_dock.py +280 -0
- aeolus1d_gui/post/solver_internals_dock.py +538 -0
- aeolus1d_gui/post/toml_view_dock.py +327 -0
- aeolus1d_gui/post/turbo_map_dock.py +262 -0
- aeolus1d_gui/runners/__init__.py +30 -0
- aeolus1d_gui/runners/qprocess_optimize_runner.py +230 -0
- aeolus1d_gui/runners/qprocess_runner.py +187 -0
- aeolus1d_gui/shell/__init__.py +1 -0
- aeolus1d_gui/shell/auci_compare_dialog.py +480 -0
- aeolus1d_gui/shell/brinkmeier_rate_widget.py +211 -0
- aeolus1d_gui/shell/case_settings_dock.py +132 -0
- aeolus1d_gui/shell/component_list.py +375 -0
- aeolus1d_gui/shell/doc_viewer.py +873 -0
- aeolus1d_gui/shell/engine_param_wizard.py +240 -0
- aeolus1d_gui/shell/form_factory.py +2315 -0
- aeolus1d_gui/shell/inspector.py +198 -0
- aeolus1d_gui/shell/named_rate_widget.py +169 -0
- aeolus1d_gui/shell/onboarding.py +452 -0
- aeolus1d_gui/shell/palette.py +177 -0
- aeolus1d_gui/shell/pid_tuning_playground.py +269 -0
- aeolus1d_gui/shell/play_controls.py +286 -0
- aeolus1d_gui/shell/runner_dock.py +1273 -0
- aeolus1d_gui/shell/schedule_widget.py +678 -0
- aeolus1d_gui/shell/settings.py +540 -0
- aeolus1d_gui/shell/theme.py +805 -0
- aeolus1d_gui/shell/time_format.py +193 -0
- aeolus1d_gui/shell/validators.py +35 -0
- 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
|
aeolus1d/_numba_flags.py
ADDED
|
@@ -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
|
+
]
|