Turbo map file format¶
Compressor and turbine maps in aeolus1d are loaded by the
from_csv and from_npz map kinds (see
theory.md §6.3 for the underlying physics
and closures-reference.md for the
full kind table). This page documents the on-disk format the
loaders expect.
Both formats describe the same physical map:
optionally extended with a third axis vane for VGT / IGV
hardware. There is no separate compressor / turbine schema —
the same columns and arrays describe both; the consuming
component (kind = "compressor" vs kind = "turbine") decides
how to interpret PR (forward expansion-ratio for the turbine
side, compression-ratio for the compressor side).
Quantities and units¶
| Symbol | Column / array | Units | Meaning |
|---|---|---|---|
PR |
PR / pr_grid |
dimensionless | Stagnation pressure ratio across the machine: p_outlet / p0_inlet (compressor) or p0_inlet / p_outlet (turbine, when fed via the inverse-PR axis). |
N_corr |
N_corr / n_corr_grid |
rad/s | Corrected shaft speed: N_corr = ω · √(T_ref / T0_inlet). |
mdot_corr |
mdot_corr / mdot_corr_table |
kg/s | Corrected mass flow: mdot_corr = ṁ · (p_ref / p0_inlet) · √(T0_inlet / T_ref). |
eta_is |
eta_is / eta_is_table |
0 – 1 | Isentropic (total-to-total) efficiency. Optional. Defaults to 0.5 when absent. |
vane |
vane / vane_grid |
0 – 1 typical | VGT / IGV vane position (0 = closed, 1 = fully open). Presence of this axis switches the loader to 3-D mode. |
Reference conditions. p_ref defaults to 1.0e5 Pa and
T_ref defaults to 298.15 K (ISO atmosphere, 1 atm / 25 °C).
Override them at load time if the source data sheet uses a
different convention (e.g. SAE J1826 uses 298 K / 100 kPa,
manufacturers sometimes use 288.15 K). Wrong reference
conditions produce a silent scaling error, not a load
failure — always check what the sheet states.
Test-gas γ / R. The TurboMap also carries gamma_ref
(default 1.4) and R_ref (default 287.05, dry air) — the
properties of the gas the map was measured on. These are used
only when the consuming [[turbo]] component opts into the
off-test-gas correction (gas_property_correction = true, with
per-component overrides map_gamma_ref / map_R_ref), which
scales the corrected speed and recovered mass flow by
√(γ_ref·R_ref / (γ·R)) so a dry-air map still reads correctly
on EGR-diluted / rich / diesel-part-load gas.
For a tabulated TurboMap they are not in the CSV file; the
CSV loader gives dry air. Since 2026-10-11 the NPZ file carries them:
save_turbo_map_npz writes gamma_ref / R_ref as optional scalars
and import_turbo_map_npz reads them back, with dry air for a file
that has neither (every NPZ written before that date; until then a
round trip reset a non-air test gas to 1.4 / 287.05). In a case run
the component level still decides: the builder sets the map's
gamma_ref / R_ref from map_gamma_ref / map_R_ref (defaults
1.4 / 287.05), whatever the file holds.
LearnedTurboMap has done the same since 2026-09-09. Its own
NPZ does carry gamma_ref / R_ref
(LearnedTurboMap.save / .load), so a surrogate trained on a
non-dry-air map keeps its test-gas identity across a round-trip
instead of silently reverting to 1.4 / 287.05. Older NPZ files stay
loadable: a missing key falls back to the dry-air default. The
reason it needed them at all is that learned_npz +
gas_property_correction = true used to raise
AttributeError: 'LearnedTurboMap' object has no attribute
'gas_property_factor' on the first step — the component protocol
said TurboMap, which LearnedTurboMap is not a subclass of, so
nothing typed the gap. from_table() carries the source map's
values across. With
gas_property_correction = false (the default) the factor is
exactly 1.0 and the map is unchanged.
CSV format (2-D, compressor / fixed-geometry turbine)¶
Plain comma-separated text with a header row. Rows may appear
in any order, but every (PR, N_corr) pair must form a complete
rectangular grid (i.e. every unique PR paired with every
unique N_corr).
PR,N_corr,mdot_corr,eta_is
1.0000,0.00,0.000000,0.1203
1.3000,0.00,0.000000,0.1944
1.6000,0.00,0.000000,0.2472
...
1.0000,500.00,0.040000,0.2192
1.3000,500.00,0.036800,0.3543
...
A working 6 × 6 example ships in the repo at
src/aeolus1d/components/turbo_data/example_centrifugal.csv.
Rules:
- The
eta_iscolumn is optional; if omitted the loader fills the table with0.5(a deliberately bad placeholder so it shows up in plots — fix the data, don't rely on this default). - Rows must form a complete
(N_n × N_pr)grid; a missing combination raisesValueError: rows do not form a complete regular grid. - Each axis needs at least 2 distinct values (a
single-speed or single-PR table fails at
TurboMapconstruction, not at CSV parse time). - Duplicate
(PR, N_corr)rows silently overwrite — the last occurrence wins. Don't rely on this. - Numbers parse via Python
float(); locales using a comma as the decimal separator must be converted first. - No comments / blank lines / extra trailing columns — anything beyond the header columns is ignored, but rows that don't parse as floats will raise.
CSV format (3-D, VGT / IGV)¶
Add a vane column. The loader auto-detects the third axis and
expects a complete (N_n × N_pr × N_vane) grid.
PR,N_corr,vane,mdot_corr,eta_is
1.0,1000.0,0.2,0.050,0.55
2.0,1000.0,0.2,0.030,0.62
1.0,1000.0,0.6,0.080,0.65
2.0,1000.0,0.6,0.055,0.71
1.0,1000.0,1.0,0.110,0.68
2.0,1000.0,1.0,0.080,0.73
1.0,2000.0,0.2,0.100,0.60
...
The resulting TurboMap carries has_vane_axis = True and is
queried via trilinear interpolation
(map.lookup(PR, N_corr, vane=v)). The component's
vane_open field then drives the runtime vane position — on
the Turbine (VGT vane) and, symmetrically, on the
Compressor (IGV position). See
examples/55_vgt_3d_turbo.toml for a 3-D vane map wired to a
boost-pressure controller (that case builds its map from the
synthetic_vgt_turbine factory, but the from_csv /
from_npz loader path yields an identical 3-D TurboMap), and
examples/plots/plot_56_vgt_3d_loader.py for a self-contained
round-trip of a 3-D map through both loader formats.
For 2-D maps without a vane axis, the turbine still accepts a
vane_open setting and falls back to a linear ṁ ∝ vane
scaling (compressors have no such fallback).
NPZ format¶
A plain NumPy archive (np.savez(...)) with the following
arrays. This is the schema produced by save_turbo_map_npz,
so the simplest way to author one is:
from aeolus1d.components.turbo import TurboMap
from aeolus1d.components.turbo_csv import (
import_turbo_map_csv, save_turbo_map_npz,
)
m = import_turbo_map_csv("compressor.csv") # or build by hand
save_turbo_map_npz(m, "compressor.npz")
Required arrays¶
| Array | dtype | Shape | Notes |
|---|---|---|---|
n_corr_grid |
float64 | (N_n,) |
Strictly-increasing corrected speeds [rad/s]. Must have ≥ 2 points. |
pr_grid |
float64 | (N_pr,) |
Strictly-increasing pressure ratios [-]. Must have ≥ 2 points. |
mdot_corr_table |
float64 | (N_n, N_pr) 2-D or (N_n, N_pr, N_vane) 3-D |
Corrected mass flow [kg/s]. Index order is [i_n, i_pr] (or [i_n, i_pr, i_v]). |
eta_is_table |
float64 | same as mdot_corr_table |
Isentropic efficiency [-]. |
Optional arrays¶
| Array | dtype | Shape | Default |
|---|---|---|---|
vane_grid |
float64 | (N_vane,) |
absent → 2-D map; strictly-increasing, ≥ 2 points when present |
p_ref |
float64 scalar | () |
1.0e5 Pa |
T_ref |
float64 scalar | () |
298.15 K |
gamma_ref |
float64 scalar | () |
1.4 (written since 2026-10-11) |
R_ref |
float64 scalar | () |
287.05 J/(kg K) (written since 2026-10-11) |
Presence of vane_grid triggers the 3-D path: both tables
must then be 3-D. Mismatched shapes will fail at construction.
TurboMap.__post_init__ validates the axes eagerly: each
interpolation axis must be strictly increasing and carry
at least 2 points (a single-point axis raises
ValueError: n_corr_grid must have at least 2 points), and the
eta_is_table shape must match mdot_corr_table exactly. The
same constraints apply to CSV-loaded maps, since the axes are
built from the unique sorted column values.
NPZ is preferred over CSV for large maps — load is faster, file
is smaller, round-tripping a synthetic map preserves it bit-
exactly (see tests/integration/test_turbo_csv_npz.py).
Wiring into a TOML case¶
Reference the file from a [turbo.map] sub-block:
[[turbo]]
id = "compressor_HP"
kind = "compressor"
inlet_pipe = "intake"
inlet_end = "right"
outlet_pipe = "manifold"
outlet_end = "left"
A_inlet = 1.0e-3
A_outlet = 1.0e-3
omega = 2000.0 # rad/s, initial shaft speed
shaft = "shaft_main"
[turbo.map]
kind = "from_csv" # or "from_npz"
csv_path = "data/compressor.csv"
# npz_path = "data/compressor.npz" # when kind = "from_npz"
Paths are resolved relative to the directory of the TOML file when given as a relative path; absolute paths are taken verbatim.
The full [turbo.map] field list (with their defaults) is
documented in closures-reference.md.
For the from_csv and from_npz kinds, only csv_path /
npz_path is required — the synthetic_* parameters
(n_corr_max, pr_max, eta_peak, vane_levels, …) are
ignored.
Built-in examples¶
| Path | Purpose |
|---|---|
src/aeolus1d/components/turbo_data/example_centrifugal.csv |
2-D centrifugal-compressor sample, 6 × 6 grid. |
src/aeolus1d/components/turbo_data/example_centrifugal.npz |
Same data, NPZ form. |
examples/30_learned_turbo_map.toml |
TOML using the learned_npz kind (the bundled MLP surrogate); structurally the same [turbo.map] block. |
examples/55_vgt_3d_turbo.toml |
TOML wiring a 3-D VGT map (vane axis) to a boost-pressure controller; drives vane_open at runtime. |
examples/plots/plot_56_vgt_3d_loader.py |
Round-trips a synthetic 3-D VGT map through both the CSV and NPZ loaders and overlays reloaded vs source lookups. |
tests/integration/test_turbo_csv_npz.py |
Canonical loader contract (regular-grid validation, round-trip, 2-D / 3-D detection). |
Common pitfalls¶
- Wrong reference conditions. Mass-flow correction folds
√T_refand1/p_refinto the table; mismatched references appear as a constant flow-scaling bias, not a load failure. - Speed in RPM, not rad/s. Many vendor sheets list
N_corrin RPM. Convert to rad/s (× 2π / 60) before writing the CSV — the loader does not unit-check. - PR for turbines. The turbine map physics uses inverse PR
(
p0_in / p_out) as the abscissa. The CSV column is still calledPR; populate it with whatever convention your component expects. Use the synthetic_turbinekinds as a reference for orientation. - Missing grid points. A CSV that's missing one
(PR, N_corr)combination raises immediately. Densify the table — do not interpolate by hand and pad zeros. eta_is = 0.5everywhere. A common symptom of forgetting theeta_iscolumn (loader fills with0.5).- Trailing blank rows. Trim them; an empty row will be parsed as an empty dict and skipped, but trailing whitespace/encoding artefacts (e.g. UTF-8 BOM in the header) break the column-name match.