Skip to content

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:

\[ (\mathrm{PR},\; N_\text{corr}) \;\longrightarrow\; (\dot m_\text{corr},\; \eta_\text{is}) \]

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_is column is optional; if omitted the loader fills the table with 0.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 raises ValueError: 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 TurboMap construction, 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_ref and 1/p_ref into 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_corr in 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 called PR; populate it with whatever convention your component expects. Use the synthetic _turbine kinds 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.5 everywhere. A common symptom of forgetting the eta_is column (loader fills with 0.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.