Vehicle Longitudinal Dynamics in Python: How Much Power an Electric Scooter Need?

Goal: Build a road load model for an electric two-wheeler and use it to answer one question: how much force and power does the motor need at each speed? Every later post in this Simulation series reuses this model.

The Physics

To move at speed v, the wheel must overcome four forces:

  • Rolling resistance: Froll = Crr × m × g × cos(θ)
  • Aerodynamic drag: Faero = 0.5 × ρ × Cd × A × v²
  • Grade: Fgrade = m × g × sin(θ)
  • Inertia: Finertia = (m + mrot) × a

Total tractive force is the sum of the four. Power at the wheel is force times speed. Power at the motor shaft is wheel power divided by drivetrain efficiency (ηdt).

Assumptions

ModeledIgnored
Constant rolling resistance coefficientSpeed dependence of Crr and tyre pressure effects
Quadratic aero drag in still airHeadwind and rider posture changes
Constant drivetrain efficiencyEfficiency varying with torque and speed (covered in a later post)
Rotating inertia as an equivalent extra massDetailed wheel and rotor inertia split
Straight-line motion on a constant gradeCornering, wheel slip, load transfer

Parameters

SymbolValueUnitNote
mveh110kgKerb mass including battery (typical electric scooter)
mrider75kgRider
mrot6kgEquivalent mass of wheels and rotor
Crr0.015-Typical road scooter tyres on asphalt
Cd0.90-Upright rider and scooter body
A0.65m²Frontal area including rider
ρ1.18kg/m³About 30 °C at 600 m altitude
ηdt0.92-Motor shaft to wheel
g9.81m/s²Gravitational acceleration

These are representative values, not a specific vehicle. Replace them with your own and rerun the script.

The Code

Copy this into a file called road_load.py. It needs only NumPy and Matplotlib.

"""Road load model for an electric 2-wheeler. Mobility Decoder, Simulation #1."""
import numpy as np
import matplotlib.pyplot as plt

# ---- Parameters (SI units) ----
m_veh, m_rider = 110.0, 75.0      # kg, kerb mass incl. battery, and rider
m = m_veh + m_rider               # total mass, kg
m_rot = 6.0                       # kg, equivalent mass of rotating parts (wheels, rotor)
g, rho = 9.81, 1.18               # m/s^2, air density in kg/m^3 (about 30 C, 600 m altitude)
Crr = 0.015                       # rolling resistance coefficient
Cd, A = 0.90, 0.65                # drag coefficient, frontal area in m^2 (rider included)
eta_dt = 0.92                     # drivetrain efficiency, motor shaft to wheel

def road_load(v, grade=0.0, a=0.0):
    """Tractive force (N) at the wheel for speed v (m/s), grade (rise/run), accel a (m/s^2)."""
    theta = np.arctan(grade)
    f_roll = Crr * m * g * np.cos(theta)
    f_aero = 0.5 * rho * Cd * A * v**2
    f_grade = m * g * np.sin(theta)
    f_inertia = (m + m_rot) * a
    return f_roll + f_aero + f_grade + f_inertia

v_kmh = np.linspace(0, 80, 161)
v = v_kmh / 3.6
grades = [0.0, 0.05, 0.10, 0.15]

fig, ax = plt.subplots(1, 2, figsize=(11, 4.2))
for gr in grades:
    F = road_load(v, grade=gr)
    ax[0].plot(v_kmh, F, label=f"grade {gr*100:.0f}%")
    ax[1].plot(v_kmh, F * v / 1000, label=f"grade {gr*100:.0f}%")
F_acc = road_load(v, a=1.0)
ax[0].plot(v_kmh, F_acc, "k--", label="flat, a = 1 m/s$^2$")
ax[1].plot(v_kmh, F_acc * v / 1000, "k--", label="flat, a = 1 m/s$^2$")
ax[0].set(xlabel="Speed (km/h)", ylabel="Tractive force at wheel (N)", title="Force needed vs speed")
ax[1].set(xlabel="Speed (km/h)", ylabel="Power at wheel (kW)", title="Power needed vs speed")
for a_ in ax: a_.grid(alpha=0.3); a_.legend()
plt.tight_layout(); plt.savefig("road_load.png", dpi=150)

# ---- Quick numbers for the post ----
for vk in (30, 40, 60, 80):
    vv = vk / 3.6
    print(f"{vk} km/h flat: F={road_load(vv):.1f} N, wheel P={road_load(vv)*vv/1000:.2f} kW, "
          f"shaft P={road_load(vv)*vv/1000/eta_dt:.2f} kW")
for gr in grades[1:]:
    vv = 30 / 3.6
    print(f"30 km/h, {gr*100:.0f}% grade: F={road_load(vv, gr):.0f} N, shaft P={road_load(vv, gr)*vv/1000/eta_dt:.2f} kW")
vv = 40/3.6
print(f"40 km/h, a=1: shaft P={road_load(vv, 0, 1.0)*vv/1000/eta_dt:.2f} kW")
# top speed for a given shaft power
for P in (3.0, 4.0, 6.0):
    vs = np.linspace(1, 40, 4000)
    Pw = road_load(vs) * vs / 1000 / eta_dt
    print(f"Shaft power {P} kW -> top speed {vs[np.argmin(abs(Pw-P))]*3.6:.0f} km/h")
# speed where aero force equals rolling force
print("Crossover speed (aero = rolling):", round(np.sqrt(2*Crr*m*g/(rho*Cd*A))*3.6,1), "km/h")

Download:road_load.py

Results

Tractive force and power versus speed for an electric two-wheeler at several grades

Left: tractive force at the wheel versus speed. Right: power at the wheel versus speed. Solid lines are constant speed at 0, 5, 10 and 15% grade. The dashed line is a flat road with 1 m/s² acceleration.

What the Plots Tell Us

  1. At low speed, force is almost flat. Rolling resistance and grade dominate. On the flat, the scooter needs only about 27 N at standstill, while a 15% slope needs about 295 N.
  2. At high speed, drag takes over. Aero force grows with v², so power grows with v³. Aero drag equals rolling resistance at about 32 km/h with these parameters.
  3. Hills cost more than speed at city pace. At 30 km/h, a 10% grade needs 2.10 kW and a 15% grade needs 2.90 kW, versus 0.46 kW on the flat.
  4. Acceleration is the real power hog. Accelerating at 1 m/s² at 40 km/h needs 3.15 kW, almost four times the 0.84 kW needed to hold that speed.

Flat road, constant speed:

SpeedForce at wheelPower at wheelPower at motor shaft
30 km/h51 N0.43 kW0.46 kW
40 km/h70 N0.78 kW0.84 kW
60 km/h123 N2.05 kW2.23 kW
80 km/h198 N4.39 kW4.77 kW

Top speed from available shaft power (flat road): 3 kW gives about 67 km/h, 4 kW gives about 75 km/h, and 6 kW gives about 87 km/h.

Sanity Check

Electric scooters with roughly 4 kW peak motors are typically rated for top speeds of about 70 to 80 km/h, which matches the model's 75 km/h. If you have a datasheet for your own vehicle, enter its mass and top speed and check that the model agrees within about 10%. Differences usually come from Cd × A, the least certain parameter here.

Limitations

  • Crr is not constant in reality. It rises with speed and falls with tyre pressure.
  • Peak motor power is not continuous power. A motor can reach top speed briefly but may overheat if held there. The thermal posts later in the series cover this.
  • There is no wheel slip or traction limit, so 1 m/s² may not be achievable on wet roads.

What Is Next?

Post 2 uses this model to size motor power for a target top speed and gradeability, and computes the 0 to 40 km/h acceleration time.