Vehicle Parameter Sensitivities at Spa using a Single Track Model¶
This notebook documents a full local sensitivity study on Spa-Francorchamps using the AD-first API run_lap_sensitivity_study(...).
KPIs:
- Lap time (
lap_time_s) - Energy consumption (
energy_kwh)
1. Engineering Question¶
Which of the selected vehicle parameters has the strongest influence on lap time and energy in the current quasi-static setup at Spa?
We vary:
- Vehicle mass
- Center-of-gravity height
- Yaw inertia
- Drag coefficient
2. Computation Method¶
Local sensitivity:
$$ S_i = \frac{\partial y}{\partial p_i} $$
Absolute KPI change for a +10% parameter variation:
$$ \Delta y_{+10\%} \approx S_i \cdot (0.10 \cdot p_{i,0}) $$
This is then plotted as Delta lap time [s] and Delta energy [Wh].
3. Solver and Model Assumptions¶
- Quasi-static forward/backward speed-profile solver
- Torch backend (autodiff default)
- Single-track vehicle model
Interpretation note:
Parameters that mainly act through transient state dynamics can appear near zero in this workflow.
from __future__ import annotations
from pathlib import Path
import sys
import matplotlib.pyplot as plt
import pandas as pd
def find_repo_root(start: Path) -> Path:
for candidate in (start, *start.parents):
if (candidate / "pyproject.toml").exists() and (candidate / "src").exists():
return candidate
raise RuntimeError("Could not locate repository root from current working directory")
repo_root = find_repo_root(Path.cwd())
if str(repo_root) not in sys.path:
sys.path.insert(0, str(repo_root))
examples_sensitivity = repo_root / "examples" / "sensitivity"
if str(examples_sensitivity) not in sys.path:
sys.path.insert(0, str(examples_sensitivity))
from apexsim.analysis import (
SensitivityRuntime,
SensitivityStudyParameter,
run_lap_sensitivity_study,
)
from apexsim.simulation import build_simulation_config
from apexsim.tire import default_axle_tire_parameters
from apexsim.track import load_track_csv
from apexsim.vehicle import SingleTrackPhysics, build_single_track_model
from common import example_vehicle_parameters, sensitivity_output_root, spa_track_path
pd.set_option("display.max_columns", 40)
pd.set_option("display.width", 180)
variation_pct = 10.0
track = load_track_csv(spa_track_path())
vehicle = example_vehicle_parameters()
tires = default_axle_tire_parameters()
physics = SingleTrackPhysics()
simulation_config = build_simulation_config(
compute_backend="torch",
torch_device="cpu",
torch_compile=False,
max_speed=115.0,
)
parameter_definitions = [
SensitivityStudyParameter(name="mass", target="vehicle.mass", label="Vehicle mass"),
SensitivityStudyParameter(name="cg_height", target="vehicle.cg_height", label="Center of gravity height"),
SensitivityStudyParameter(name="yaw_inertia", target="vehicle.yaw_inertia", label="Yaw inertia"),
SensitivityStudyParameter(name="drag_coefficient", target="vehicle.drag_coefficient", label="Drag coefficient"),
]
model = build_single_track_model(
vehicle=vehicle,
tires=tires,
physics=physics,
)
pd.DataFrame(
{
"parameter": [p.name for p in parameter_definitions],
"target": [p.target for p in parameter_definitions],
"variation_used": [f"+/-{variation_pct:.0f}%"] * len(parameter_definitions),
}
)
study_result = run_lap_sensitivity_study(
track=track,
model=model,
simulation_config=simulation_config,
parameters=parameter_definitions,
label="Spa-Francorchamps",
)
long_df = study_result.to_dataframe().sort_values(["objective", "parameter"], kind="stable")
long_df["absolute_delta_plus"] = long_df["predicted_objective_plus"] - long_df["objective_value"]
long_df["absolute_delta_minus"] = long_df["predicted_objective_minus"] - long_df["objective_value"]
long_df[[
"objective",
"parameter_label",
"objective_value",
"sensitivity_raw",
"sensitivity_pct_per_pct",
"absolute_delta_plus",
"absolute_delta_minus",
]]
4. Result Plots in Absolute KPI Units¶
We plot the estimated KPI change for a +10% parameter increase:
Delta lap time [s]Delta energy [Wh]
output_dir = sensitivity_output_root() / "spa_single_track"
output_dir.mkdir(parents=True, exist_ok=True)
long_df.to_csv(output_dir / "sensitivities_long.csv", index=False)
study_result.to_pivot().sort_index(kind="stable").to_csv(output_dir / "sensitivities_pivot.csv")
plot_df = long_df[["parameter_label", "objective", "absolute_delta_plus"]].pivot(
index="parameter_label",
columns="objective",
values="absolute_delta_plus",
)
fig, axes = plt.subplots(1, 2, figsize=(12.0, 4.5), constrained_layout=True)
plot_df["lap_time_s"].plot(
kind="bar",
ax=axes[0],
color="#1565c0",
title=f"Lap-time delta for +{variation_pct:.0f}% parameter variation",
)
(plot_df["energy_kwh"] * 1000.0).plot(
kind="bar",
ax=axes[1],
color="#2e7d32",
title=f"Energy delta for +{variation_pct:.0f}% parameter variation",
)
axes[0].set_ylabel("Delta lap time [s]")
axes[1].set_ylabel("Delta energy [Wh]")
for axis in axes:
axis.tick_params(axis="x", rotation=20)
axis.grid(alpha=0.25, axis="y")
plot_path = output_dir / "sensitivity_bars.png"
fig.savefig(plot_path, dpi=160)
plt.close(fig)
print(f"Artifacts written to: {output_dir}")
plot_df
5. Validation of the Observed Near-Zero Sensitivities¶
As a robustness check, we compare AD and finite differences specifically for cg_height and yaw_inertia.
zero_check_parameters = [
SensitivityStudyParameter(name="cg_height", target="vehicle.cg_height", label="Center of gravity height"),
SensitivityStudyParameter(name="yaw_inertia", target="vehicle.yaw_inertia", label="Yaw inertia"),
]
fd_check = run_lap_sensitivity_study(
track=track,
model=model,
simulation_config=simulation_config,
parameters=zero_check_parameters,
runtime=SensitivityRuntime(method="finite_difference"),
)
comparison_rows = []
for objective in ("lap_time_s", "energy_kwh"):
for name in ("cg_height", "yaw_inertia"):
comparison_rows.append(
{
"objective": objective,
"parameter": name,
"autodiff": study_result.sensitivity_results[objective].sensitivities[name],
"finite_difference": fd_check.sensitivity_results[objective].sensitivities[name],
}
)
pd.DataFrame(comparison_rows)
6. Engineering Interpretation for Spa¶
- Mass and drag dominate the KPI response in this setup.
- CoG height and yaw inertia are near zero and are consistently confirmed by AD and FD.
- This is not a contradiction of vehicle dynamics theory; it reflects the active quasi-static solver path used here.
Conclusion: as a local engineering study, the result is internally consistent and clearly shows which parameters are active in the current model path.