Physics Whitepaper

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).

State vector
[x, v, ω_e, T_tyre, T_clutch, gear]
x — position (m)v — velocity (m/s)ω_e — engine RPM (rad/s)T_tyre — tyre temp (°C)T_clutch — clutch temp (°C)gear — gear index (1–6)
Force balance at each timestep
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.

01
Engine torque model

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.

submodels/engine.py
02
Forced induction

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.

submodels/forced_induction.py
03
Drivetrain

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).

submodels/drivetrain.py
04
Clutch dynamics

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.

submodels/clutch.py
05
Chain & sprocket

Sprocket ratio applied to rear-wheel torque. Chain stretch factor (efficiency reduction over service interval). Verification that sprocket tooth counts are physically valid.

submodels/chain.py
06
Pacejka tire model

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.

submodels/tire.py
07
Aerodynamics

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.

submodels/aero.py
08
Weight transfer

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.

submodels/weight_transfer.py
09
Rider dynamics

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).

submodels/rider_dynamics.py
10
Staging & launch

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.

submodels/staging.py
11
Environmental

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.

submodels/environmental.py
12
Braking

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.

submodels/braking.py
13
Wheelie model

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.

submodels/wheelie.py
14
Parasitic losses

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.

submodels/parasitic.py
15
Rolling resistance

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.

submodels/rolling_resistance.py

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.