Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

What is a SimDrive Object?

A SimDrive object is the central simulation unit in FASTSim. It combines a Vehicle (what the vehicle is) and a Cycle (what the vehicle does) into a single simulation scenario.

Calling sd.walk() steps the vehicle through the drive cycle one time step at a time, computing energy flows, achieved speed, and component states at each step.

import fastsim

Creating a SimDrive

SimDrive takes a Vehicle and a Cycle as arguments. Optionally, a SimParams object can be passed to customize solver behavior (see Editing Simulation Parameters).

Load a bundled vehicle and drive cycle, then construct the simulation object:

# Load a bundled vehicle
veh = fastsim.Vehicle.from_resource("2012_Ford_Fusion.yaml")

# Load a bundled drive cycle
cyc = fastsim.Cycle.from_resource("udds.csv")

# Combine into a simulation scenario
sd = fastsim.SimDrive(veh, cyc)
print(sd)
Fetching long content....

Running the Simulation

Call sd.walk() to execute the simulation. This steps through every time step in the cycle and computes the vehicle’s energy flows, achieved speed, and component states.

walk() applies powertrain-specific corrections automatically:

  • Conventional: simulates once.

  • BEV / PHEV: sets initial SOC to the maximum before simulating.

  • HEV: iterates until the initial and final SOC are balanced (charge-sustaining operation).

If you need to run exactly one iteration without any of those corrections (e.g., you have already set an initial SOC yourself in the RES state), use sd.walk_once() instead.

sd = fastsim.SimDrive(veh, cyc)
sd.walk()
print("Simulation complete.")
Simulation complete.

Extracting Results

After walk(), the simulation results are stored inside the SimDrive object. There are two primary ways to access them.

to_dataframe()

Returns a tidy pandas.DataFrame with one row per time step and one column per tracked quantity. Column names use dot-separated paths that mirror the object hierarchy (cyc.time_seconds, veh.history.speed_ach_meters_per_second, etc.).

This is the most convenient format for plotting and analysis.

df = sd.to_dataframe()
print(f"{len(df)} time steps, {len(df.columns)} columns")
print("\nFirst few column names:")
print(df.columns.tolist()[:8])
1370 time steps, 62 columns

First few column names:
['veh.pt_type.Conv.fc.history.i', 'veh.pt_type.Conv.fc.history.pwr_out_max_watts', 'veh.pt_type.Conv.fc.history.pwr_prop_max_watts', 'veh.pt_type.Conv.fc.history.eff', 'veh.pt_type.Conv.fc.history.pwr_prop_watts', 'veh.pt_type.Conv.fc.history.energy_prop_joules', 'veh.pt_type.Conv.fc.history.pwr_aux_watts', 'veh.pt_type.Conv.fc.history.energy_aux_joules']

Plot achieved speed against the target cycle speed to confirm the vehicle followed the trace:

import plotly.graph_objects as go

TARGET_COLOR = "#4D4D4D"
ACHIEVED_COLOR = "#0072B2"

fig = go.Figure()
fig.add_trace(
    go.Scatter(
        x=df["cyc.time_seconds"],
        y=df["cyc.speed_meters_per_second"],
        name="Target",
        line={"dash": "dash", "width": 4, "color": TARGET_COLOR},
    )
)
fig.add_trace(
    go.Scatter(
        x=df["cyc.time_seconds"],
        y=df["veh.history.speed_ach_meters_per_second"],
        name="Achieved",
        line={"color": ACHIEVED_COLOR},
    )
)
fig.update_layout(
    xaxis_title="Time [s]",
    yaxis_title="Speed [m/s]",
    title="Target vs. Achieved Speed (UDDS)",
)
fig.show()
Loading...
all(df["veh.history.cyc_met"])
True

FASTSim also has options for when the vehicle cannot meet the physical demands of the drive cycle. See Handling Trace Miss for more information.

to_pydict()

to_pydict(flatten=True) returns a flat dictionary mapping dotted key paths to scalar values (cumulative totals and final states). This is useful for extracting summary metrics such as total fuel consumed or total distance driven.

sd_dict = sd.to_pydict(flatten=True)

fuel_joules = sd_dict["veh.pt_type.Conv.fc.state.energy_fuel_joules"]
dist_meters = sd_dict["veh.state.dist_meters"]

KWH_PER_GGE = 33.7
METERS_PER_MILE = 1609.34

fuel_kwh = fuel_joules / 3.6e6
miles = dist_meters / METERS_PER_MILE
mpg = miles / (fuel_kwh / KWH_PER_GGE)

print(f"Distance driven:  {miles:.2f} miles")
print(f"Fuel consumed:    {fuel_kwh:.3f} kWh  ({fuel_kwh / KWH_PER_GGE:.4f} gal)")
print(f"Fuel economy:     {mpg:.1f} mpgge")
Distance driven:  7.45 miles
Fuel consumed:    7.303 kWh  (0.2167 gal)
Fuel economy:     34.4 mpgge

Controlling State History with set_save_interval

Predefined vehicles default to save_interval = 1, which records internal state at every time step. This is required for to_dataframe() and per-step plots.

You can change this behavior:

ValueBehavior
1Record every time step (default for predefined vehicles)
NRecord every N-th time step
NoneDisable history recording entirely (fastest; only final/cumulative state is available)

Use None when you only need summary results (accumulated energy, distance, etc.) and want to reduce memory usage and runtime:

veh_no_hist = fastsim.Vehicle.from_resource("2012_Ford_Fusion.yaml")
veh_no_hist.set_save_interval(None)  # disable per-step history

sd_no_hist = fastsim.SimDrive(veh_no_hist, cyc)
sd_no_hist.walk()

sd_dict_no_hist = sd_no_hist.to_pydict(flatten=True)
fuel_kwh_no_hist = sd_dict_no_hist["veh.pt_type.Conv.fc.state.energy_fuel_joules"] / 3.6e6
miles_no_hist = sd_dict_no_hist["veh.state.dist_meters"] / METERS_PER_MILE
mpg_no_hist = miles_no_hist / (fuel_kwh_no_hist / KWH_PER_GGE)
print(f"Fuel economy (no history): {mpg_no_hist:.1f} mpgge")
Fuel economy (no history): 34.4 mpgge

Without saving history, there will be no time-series simulation data to inspect.

sd_no_hist.to_dataframe().empty
True

Running Multiple Simulations

To compare the same vehicle on different cycles, create new SimDrive objects with the shared vehicle. This is efficient since the vehicle parameters are only loaded once and reused.

As an example, here the vehicle is simulated on both city (UDDS) and highway (HWFET) cycles:

# Load drive cycles
cyc_udds = fastsim.Cycle.from_resource("udds.csv")
cyc_hwfet = fastsim.Cycle.from_resource("hwfet.csv")

# Simulate UDDS cycle
sd_udds = fastsim.SimDrive(veh, cyc_udds)
sd_udds.walk()

# Simulate HWFET cycle
sd_hwfet = fastsim.SimDrive(veh, cyc_hwfet)
sd_hwfet.walk()

# UDDS calculations
sd_udds_dict = sd_udds.to_pydict(flatten=True)
fuel_kwh_udds = sd_udds_dict["veh.pt_type.Conv.fc.state.energy_fuel_joules"] / 3.6e6
miles_udds = sd_udds_dict["veh.state.dist_meters"] / METERS_PER_MILE
mpg_udds = miles_udds / (fuel_kwh_udds / KWH_PER_GGE)

# HWFET calculations
sd_hwfet_dict = sd_hwfet.to_pydict(flatten=True)
fuel_kwh_hwfet = sd_hwfet_dict["veh.pt_type.Conv.fc.state.energy_fuel_joules"] / 3.6e6
miles_hwfet = sd_hwfet_dict["veh.state.dist_meters"] / METERS_PER_MILE
mpg_hwfet = miles_hwfet / (fuel_kwh_hwfet / KWH_PER_GGE)

# Print results
print(f"UDDS (city) fuel economy: {mpg_udds:.1f} mpgge")
print(f"HWFET (highway) fuel economy: {mpg_hwfet:.1f} mpgge")
UDDS (city) fuel economy: 34.4 mpgge
HWFET (highway) fuel economy: 47.0 mpgge

Note that these outputs are unadjusted fuel economy and do not accurately reflect the true label fuel economy. For information on matching label fuel economy with FASTSim, see Comparing to Label Fuel Economy.