Vehicle-system drive cycles¶
aeolus1d carries a vehicle-system layer alongside the 1-D gas-network solver: a longitudinal vehicle model, a closed-loop driver, drive-cycle profiles, a transmission/driveline with an AMT shift state machine, a parallel-hybrid powertrain, and a control/signal toolbox. This is the AVL Cruise M-style vehicle workflow — feed a speed-time target, let a driver model track it, and read back the cycle KPIs (distance, tracking error, fuel, CO₂).
A vehicle-system run is not a 1-D gas-network solve, so it has its own self-contained case format and CLI entry point:
aeolus1d drive-cycle examples/85_vehicle_drive_cycle.toml --out trace.csv
aeolus1d run is for gas-network cases; aeolus1d drive-cycle is for
vehicle-system cases. They do not share a schema.
The case file¶
Four blocks, all optional except [drive_cycle]:
[vehicle] # aeolus1d.vehicle.VehicleParameters fields
mass_kg = 1300.0
cd = 0.30
frontal_area_m2 = 2.2
wheel_radius_m = 0.30
gear_ratio = 8.0 # combined gearbox × final drive
driveline_efficiency = 0.92
crr = 0.010 # rolling-resistance coefficient
[driver] # aeolus1d.driver.DriverParameters fields
kp = 2.0 # P gain on speed error [1/s]
ki = 0.5 # I gain [1/s²]
feedforward = true # add steady road-load; PI only trims
[drive_cycle]
source = "builtin" # "builtin" | "csv" | "segments"
name = "urban_illustrative"
dt_s = 0.1 # keep kp*dt_s < 1 (PI pole 1 - kp*dt_s)
grade_rad = 0.0
[report] # optional — turns wheel work into fuel/CO₂
bsfc_g_per_kwh = 240.0
fuel_density_kg_l = 0.745 # gasoline default
fuel_co2_g_per_g = 3.09 # gasoline default (g CO₂ per g fuel)
Unknown keys in [vehicle], [driver], and [report] raise an error
(typo protection) rather than being silently ignored. The only three
keys [report] accepts are bsfc_g_per_kwh, fuel_density_kg_l, and
fuel_co2_g_per_g; the last two default to gasoline (0.745 kg/L, 3.09 g
CO₂/g) and only matter once a BSFC is supplied.
Choosing a cycle¶
The [drive_cycle] source selects how the speed-time profile is built:
source |
What it does | Use for |
|---|---|---|
"builtin" |
A bundled illustrative cycle by name (urban_illustrative, highway_illustrative). |
Demos / smoke tests. Not regulatory. |
"csv" |
Load a two-column time,speed table from path (speed_unit = "kmh" or "mps"; column indices default to time_col = 0, speed_col = 1, and a header row is assumed via has_header = true). |
Official WLTP / NEDC / FTP-75 / JC08 cycles. |
"segments" |
Build an exact synthetic cycle from inline idle/accelerate/cruise/decelerate phases. | Reproducible custom cycles. |
Regulatory cycles are not bundled. The WLTP / NEDC / FTP-75 / JC08 speed-time tables are public data but long empirical traces; aeolus1d does not ship them (and won't fabricate them). Drop the official CSV next to your case and point
source = "csv",path = "..."at it. Naming a regulatory cycle undersource = "builtin"returns a pointed error telling you to use the CSV path.
A segments cycle is defined inline:
[drive_cycle]
source = "segments"
name = "my_synthetic"
dt_s = 0.1
segments = [
{ kind = "idle", duration_s = 8.0 },
{ kind = "accelerate", duration_s = 12.0, target_kmh = 50.0 },
{ kind = "cruise", duration_s = 30.0, target_kmh = 50.0 },
{ kind = "decelerate", duration_s = 10.0, target_kmh = 0.0 },
]
What it reports¶
aeolus1d drive-cycle prints the cycle KPIs and, with --out, writes the
per-step trace (t_s, v_target_mps, v_actual_mps, engine_torque_Nm,
brake_force_N) as CSV:
Drive cycle 'urban_illustrative': 150 s, dt=0.1 s
distance : 0.777 km
avg speed : 18.64 km/h
max speed : 30.99 km/h
tracking RMSE : 0.164 m/s
positive wheel W : 0.067 kWh
fuel : 3.007 L/100km # only with [report] bsfc
CO₂ : 69.22 g/km
Fuel and CO₂ are estimated from the positive engine-side work (the
wheel work divided by the driveline efficiency) via the supplied
brake-specific fuel consumption (bsfc_g_per_kwh) — a first-order
energy-balance estimate, not a fuel map. For a fuel map, drive the
engine model directly (see the engine validation suites).
Electrified (hybrid) drive cycles — W5¶
Add a [powertrain] block and the same case becomes a parallel
hybrid: the driver's per-step engine-torque demand is split between the
engine and an e-motor by a power-split controller, and the battery
state-of-charge is coulomb-counted. aeolus1d drive-cycle auto-detects the
block and reports the W5 deliverable — SoC, electrical energy used (and
regen recovered), and the engine-share fuel + tailpipe CO₂:
[powertrain]
[powertrain.motor] # aeolus1d.hybrid.EMotor fields
T_max_Nm = 200.0
P_max_W = 60.0e3
eta_drive = 0.92
eta_regen = 0.85
[powertrain.battery] # aeolus1d.hybrid.Battery fields
capacity_Ah = 50.0
SoC = 0.6 # initial state-of-charge
R_internal_Ohm = 0.1
[powertrain.controller] # aeolus1d.hybrid.HybridController fields
strategy = "load_following" # or "fixed_fraction" / "engine_only" / "motor_only"
motor_fraction = 0.3 # split used by the "fixed_fraction" strategy
engine_min_T_Nm = 30.0
regen_enabled = true
The four split strategies are load_following (default — the engine
covers demand above engine_min_T_Nm, the motor trims the rest),
fixed_fraction (the motor always takes motor_fraction of the
demand), engine_only, and motor_only. Unknown keys in any of the
three [powertrain.*] sub-tables — or an unknown sub-table name — raise.
Electrified drive cycle 'urban_illustrative': 150 s, dt=0.1 s
distance : 0.785 km
tracking RMSE : 0.189 m/s
SoC : 60.0% -> 59.8%
electrical used : 0.058 kWh (regen 0.029 kWh)
engine fuel : 1.087 L/100km
tailpipe CO₂ : 25.02 g/km
The vehicle longitudinal dynamics are identical to the conventional run
(it moves on the total engine + motor torque); the hybrid only
allocates the source, so the difference is the energy split. The battery
is a coulomb-counted SoC + OCV-map + internal-resistance model; drive
discharges it, braking regenerates (clamped to [0, 1]). The deeper
electrochemical battery tier (cell design / aging / pack thermal) is
out of scope — import it as an FMU. See examples/86_electrified_drive_cycle.toml.
Programmatic use¶
The same machinery is a small Python API:
from aeolus1d.io.vehicle_case import load_vehicle_case, run_vehicle_case
case = load_vehicle_case("examples/85_vehicle_drive_cycle.toml")
trace, kpi = run_vehicle_case(case)
print(kpi.distance_km, kpi.fuel_l_per_100km)
A case that carries a [powertrain] block sets case.hybrid; run it
through run_electrified_case(case) instead (it returns
(trace, ElectrifiedKPIs) — SoC, electrical/regen energy, and the
engine-share fuel + CO₂). The aeolus1d drive-cycle CLI auto-selects
between the two by whether case.hybrid is None.
The underlying blocks — aeolus1d.vehicle, aeolus1d.driver,
aeolus1d.drive_cycles, aeolus1d.driveline, aeolus1d.transmission_shift,
aeolus1d.hybrid, aeolus1d.analysis.vehicle_tasks, and the
aeolus1d.control.signal_toolbox — are all usable independently for
acceleration / elasticity / gradeability / coastdown analysis tasks and
for assembling custom controllers.