How MotoQuant Simulates a Quarter Mile
A first-principles summary of the 15 sub-models, the RK4 numerical integrator, and the validation methodology behind every ET prediction.
Overview
MotoQuant solves the motorcycle drag problem as a system of coupled ODEs. The state vector at each timestep is [position, velocity, engine_RPM, tyre_temperature, clutch_temperature, gear]. The RK4 integrator advances this vector at dt = 1 ms (fixed-step mode) or dt_min = 0.5 ms / dt_max = 5 ms (adaptive mode), re-evaluating aerodynamic and traction forces at the midpoint of each step.
The simulation terminates when the bike crosses 402.336 m (the quarter mile), when elapsed time exceeds 26 s (prevents infinite loops on bikes that cannot complete), or when a NaN/Inf is detected in the state vector (which triggers an early-exit with an error flag rather than a corrupted result).
[x, v, ω_e, T_tyre, T_clutch, gear]F_net = F_traction − F_aero − F_rolling_resistance − F_grade
a = F_net / m_total
F_traction = min(T_wheel / R_tyre, μ_peak × F_z_rear × Pacejka(κ))Numerical Integrator
The fixed-step RK4 integrator runs at dt = 1 ms. For most bikes, this gives adequate accuracy — the maximum position error per step is O(dt⁵) ≈ 10⁻¹⁵ m, well within measurement noise.
The adaptive-dt solver uses a fifth-order embedded RK pair (RK45) with error control. Step size is halved when position error exceeds 10⁻⁴ m and doubled when error is below 10⁻⁶ m. The adaptive solver is most useful for clutch-heavy launch phases where the state changes rapidly and 1 ms steps may slightly mis-time the engagement point.
Aerodynamic drag is re-evaluated at the RK4 midpoint (i.e. at v + k₁/2 rather than at v) to reduce the systematic bias that naive Euler or basic RK4 produces at high velocities. This is a minor but measurable improvement for bikes that spend significant run time above 180 km/h.
The 15 Sub-Models
Each sub-model is an independent Python module in motoquant/submodels/. They communicate only through the BikeConfig dataclass and the current state vector — no global state.
Peak power + torque from spec; dyno curve injection if available. Willans mean-effective-pressure friction model for mechanical losses. 2T expansion-chamber Gaussian curve with powervalve width modulation.
Turbocharger (boost curve, compressor map, lag), supercharger (mechanical drive loss, constant boost), nitrous (injection window, jetting-mass-flow). Boost type is a per-part parameter.
Gear ratio lookup from 150+ Tier-1 service-manual-accurate families. Chain drive efficiency (0.93–0.97 depending on pitch and maintenance state). Quickshifter model (zero-torque cut window, shift time).
Wet multi-plate clutch with engagement rate (Nm/s) and slip RPM threshold. Slipper clutch branch: overrun torque reversal → spring-preload backstop. Clutch heat accumulation during engagement.
Sprocket ratio applied to rear-wheel torque. Chain stretch factor (efficiency reduction over service interval). Verification that sprocket tooth counts are physically valid.
Magic Formula for longitudinal slip force: F_x = D·sin(C·arctan(B·κ − E·(B·κ − arctan(B·κ)))). Thermal evolution: heat generation from slip, conduction to road surface, Nusselt-number convection.
F_drag = ½·ρ·Cd·A·v². Per-bike Cd (0.308–0.68) and frontal area (0.35–0.68 m²). Rider tuck transition: Cd interpolates from upright to tuck posture above a speed threshold. Wind vector support.
Longitudinal weight transfer: ΔF_z = m·a_x·h_cog / L_wb. Front axle load approaches zero at wheelie threshold. Rear axle load added to tire normal force for traction calculation.
Rider CoG height and longitudinal position; rider mass distributed across total system. Tuck factor: rider frontal area reduction at high speed. Separately tunable for different rider postures (sport tuck vs upright naked).
Rollout distance, reaction time (added post-tree), two-step launch control RPM. For 2T engines, launch RPM defaults to tuned_rpm × 0.92. For 4T, configurable launch_rpm field in BikeConfig.
Air density from ISA model: ρ = P/(R_specific·T). Power correction factor: (P_std/P) × √(T_std/T). Density altitude calculation. Surface grip multiplier (asphalt, concrete, salt flat, dry lake). Wind resistance add-in.
Used for top-speed and calibration passes. max_decel = μ_front × front_load + μ_rear × rear_load, with brake balance split. Braking from trap speed to rest for multi-run simulation.
When front axle load → 0: system transitions to rotational dynamics. Wheelie bar extends effective wheelbase, resisting the transition. Wheelie contributes angular momentum competing with forward acceleration.
Alternator (60–150 W), oil pump (60–500 W), water pump (0 or 40 W electric), valvetrain (0 for 2T, 40–250 W for 4T). All modelled as constant power drains from the crankshaft.
F_rr = Crr × m × g × cos(slope). Crr: 0.010–0.014 depending on bike class and tyre type. Slope term allows banked or inclined strip modelling.
BikeConfig: The Parameter Container
Every simulation takes a BikeConfig object — a nested Pydantic/dataclass structure with 13 parameter sections: EngineParams, ForcedInductionParams, DrivetrainParams, ClutchParams, ChainParams, TireParams, AeroParams, ChassisParams, WheelieParams, RiderParams, StagingParams, EnvironmentParams, ParasiticParams.
The spec_to_bike_config() function in scraper/importer.py converts a raw BikeSpec (scraped or hand-entered) into a validated BikeConfig. It applies displacement-scaled engine inertia, chain-pitch-inferred drivetrain parameters, category-aware tire grip defaults, and the 150+ Tier-1 gear ratio families.
Parts mods apply dotted-path notation to override specific fields (e.g. engine.peak_power_hp,drivetrain.front_sprocket). The bridge module validates every path before applying, logging a warning if the path is stale.