Solver Mathematics¶
This document explains the two solver modes available through
simulate_lap(track, model, config):
- quasi-static speed-profile solver (
solver_mode="quasi_static") - transient dynamic solver (
solver_mode="transient_oc")
Sections 1-10 cover the quasi-static formulation implemented in the shared
core (src/apexsim/simulation/_profile_core.py) and backend adapters
(src/apexsim/simulation/profile.py, src/apexsim/simulation/torch_profile.py).
Section 11 summarizes the transient formulations
(src/apexsim/simulation/transient_*.py).
The underlying equations are based on standard vehicle-dynamics and lap-time simulation formulations [1], [3].
1. Discretization and State¶
The track is represented in arc-length domain by points \(i = 0,\dots,N-1\) with:
- position \((s_i)\) [m]
- curvature \(\kappa_i\) [1/m]
- grade \(\gamma_i = dz/ds\) [-]
- banking angle \(\beta_i\) [rad]
Segment length: [ \Delta s_i = s_{i+1} - s_i, \quad i=0,\dots,N-2. ]
The solver computes a speed profile \(v_i\) [m/s], then derives:
- longitudinal acceleration \(a_{x,i}\) [m/s²]
- lateral acceleration \(a_{y,i}\) [m/s²]
- lap time \(T\) [s]
2. Lateral Envelope (Cornering Limit)¶
A lateral speed limit \(v_{\text{lat},i}\) is computed at each point. Core relation: [ |a_{y,i}| = v_i^2 |\kappa_i|. ]
Given lateral acceleration capacity \(a_{y,\text{lim},i}\): [ v_{\text{lat},i} = \begin{cases} \sqrt{a_{y,\text{lim},i}/|\kappa_i|}, & |\kappa_i| > \varepsilon \ v_{\max}, & |\kappa_i| \le \varepsilon \end{cases} ] with clipping to \([v_{\min}, v_{\max}]\).
2.1 Lateral Acceleration Capacity¶
SingleTrackModel.lateral_accel_limit(...) solves a fixed-point problem because tire force capacity depends
on normal load, and normal load depends on lateral acceleration through load transfer.
This is the quasi-steady coupled load-transfer/tire-force formulation described
in [1, Ch. 10].
For fixed speed \(v\):
- Estimate axle loads from quasi-static vertical balance using current \(a_y\) guess.
- Compute front/rear lateral tire forces at a representative peak slip angle (Pacejka Magic Formula).
- Update lateral limit: [ a_{y,\text{next}} = \max\left(a_{y,\min}, \frac{F_{y,f}+F_{y,r}}{m} + g\sin\beta\right). ]
- Repeat until \(|a_{y,\text{next}}-a_{y,\text{current}}|\le\text{tol}\) or max iterations.
3. Longitudinal Coupling via Friction Circle¶
Available longitudinal capability is reduced by lateral usage: [ \lambda_i = \sqrt{\max\left(0, 1 - \left(\frac{|a_{y,\text{req},i}|}{a_{y,\text{lim},i}}\right)^2\right)}, \quad |a_{y,\text{req},i}| = v_i^2 |\kappa_i|. ]
- Drive limit: \(a_{x,\text{drive},i} = a_{x,\text{drive,max}}\,\lambda_i\)
- Brake limit: \(a_{x,\text{brake},i} = a_{x,\text{brake,max}}\,\lambda_i\)
This is a simplified isotropic friction-circle approximation. It follows the standard combined-acceleration envelope approximation used in race-car analysis [3].
4. Forward Pass (Acceleration-Limited)¶
Starting from \(v_0\), propagate forward with kinematic relation: [ v_{i+1}^2 = v_i^2 + 2 a_{x,\text{net},i} \Delta s_i. ]
Initial-condition rule: [ v_0 = \min\left(v_{\text{lat},0}, v_{\max}, v_{\text{init}}\right), ] where \(v_{\text{init}}\) is:
RuntimeConfig.initial_speed, if provided.- otherwise the legacy fallback \(v_{\max}\).
Net acceleration model: [ a_{x,\text{net},i} = a_{x,\text{drive},i} - \frac{D(v_i)}{m} - g\,\gamma_i, ] where drag force is [ D(v) = \tfrac{1}{2}\rho c_d A v^2. ] The aerodynamic force law follows [1], [3].
Then enforce bounds: [ v_{i+1} \leftarrow \min(v_{i+1}, v_{\text{lat},i+1}, v_{\max}), \quad v_{i+1} \ge v_{\min}. ]
5. Backward Pass (Braking-Limited)¶
From the end of the lap backwards, enforce braking feasibility: [ v_i^2 = v_{i+1}^2 + 2 a_{x,\text{decel,avail},i} \Delta s_i, ] with [ a_{x,\text{decel,avail},i} = a_{x,\text{brake},i} + \frac{D(v_{i+1})}{m} + g\,\gamma_{i+1}. ]
Again clamp by lateral and global speed bounds.
6. Final Accelerations and Lap Time¶
After forward/backward constraints, the final profile is \(v_i\). Longitudinal acceleration is reconstructed by finite differences: [ a_{x,i} = \frac{v_{i+1}^2 - v_i^2}{2\Delta s_i}, \quad i=0,\dots,N-2. ]
Lateral acceleration: [ a_{y,i} = v_i^2\kappa_i. ]
Segment time is approximated with average segment speed: [ \Delta t_i = \frac{\Delta s_i}{\max\left(\tfrac{v_i+v_{i+1}}{2}, \varepsilon_v\right)}. ]
Total lap time: [ T = \sum_{i=0}^{N-2} \Delta t_i. ]
7. Numerical Convergence Controls¶
Lateral envelope convergence in solve_speed_profile(...) is configurable via
SimulationConfig.numerics:
lateral_envelope_max_iterationslateral_envelope_convergence_tolerance
Stop criterion: [ \max_i |v^{(k)}{\text{lat},i} - v^{(k-1)}{\text{lat},i}| \le \text{tol}_v. ]
The actual number of iterations used is reported as
SpeedProfileResult.lateral_envelope_iterations.
8. Physical Scope and Limitations¶
Quasi-static mode is intentionally envelope-based:
- No transient tire relaxation dynamics.
- No explicit driver/controller model in the speed-profile pass.
- Longitudinal limits are envelope-based constants (drive/brake maxima).
- Tire model uses fixed representative peak slip-angle for envelope estimation.
These simplifications keep the solver fast and stable, while preserving core constraints for lap-time studies.
The same solver routine can be used with different backends implementing
VehicleModel, including the single-track and point-mass models.
8.1 Yaw-Moment Output Semantics¶
LapResult.yaw_moment is reported with solver-mode-consistent semantics:
- quasi-static mode: the model is solved in steady-state envelope form, so yaw-moment output is set to zero by assumption.
- transient mode: yaw moment is reported as the dynamic residual [ M_z = I_z \dot r, ] with yaw acceleration \(\dot r\) reconstructed from transient yaw-rate and time traces.
For steady-state cornering this residual should approach zero in the interior of the lap (excluding seam transients), which provides a direct stability and convergence indicator.
9. Equation-to-Code Mapping¶
- \(a_{y,i} = v_i^2\kappa_i\):
src/apexsim/simulation/_profile_core.py(solve_speed_profile_core)- \(v_{\text{lat},i} = \sqrt{a_{y,\text{lim},i}/|\kappa_i|}\) (with clipping):
src/apexsim/simulation/_profile_core.py(lateral_speed_limit_core)- Friction-circle scaling \(\lambda_i\) (vehicle-model dependent):
src/apexsim/vehicle/single_track_model.py(_friction_circle_scale)- Forward pass \(v_{i+1}^2 = v_i^2 + 2a\Delta s\):
src/apexsim/simulation/_profile_core.py(solve_speed_profile_core)- Backward pass braking feasibility:
src/apexsim/simulation/_profile_core.py(solve_speed_profile_core)- Lap-time accumulation \(T = \sum \Delta t_i\):
src/apexsim/simulation/_profile_core.py(solve_speed_profile_core)- Segment time model \(\Delta t_i = \Delta s_i / \bar v_i\):
src/apexsim/simulation/_profile_core.py(solve_speed_profile_core)- Lateral limit fixed-point update:
src/apexsim/vehicle/single_track_model.py(lateral_accel_limit)- Lateral envelope fixed-point convergence in speed domain:
src/apexsim/simulation/_profile_core.py(solve_speed_profile_core)- Vehicle-model API contract consumed by the solver:
src/apexsim/simulation/model_api.py(VehicleModel)src/apexsim/simulation/profile.pyandsrc/apexsim/simulation/torch_profile.py(backend adapters calling shared core)
10. Differentiable Torch Solver API¶
For gradient-based workflows (e.g. autodiff sensitivities), ApexSim exposes:
apexsim.simulation.solve_speed_profile_torch(track, model, config)
This API returns tensor-valued outputs (TorchSpeedProfileResult) and keeps the
autograd graph intact for downstream backward() calls.
Current constraint:
RuntimeConfig.torch_compilemust beFalseforsolve_speed_profile_torch.
11. Transient Solver Mathematics¶
Transient mode supports two driver/control strategies over fixed track arc-length samples:
driver_model="pid"(default): closed-loop PID driver tracking a quasi-static reference profile.driver_model="optimal_control": minimum-time optimal-control formulation.
The OC objective is: [ \min_{u} \; T + w_{\text{lat}} J_{\text{lat}} + w_{\text{trk}} J_{\text{trk}} + w_{\text{sm}} J_{\text{sm}}. ]
11.1 State and controls¶
- Point-mass transient state: [ x_i = [v_i] ]
- Single-track transient state: [ x_i = [v_{x,i}, v_{y,i}, r_i] ] where \(r\) is yaw rate.
Controls:
- Point-mass: longitudinal command \(a_{x,\text{cmd},i}\).
- Single-track: \(a_{x,\text{cmd},i}\) and steering command \(\delta_i\).
Single-track control bounds are physical model inputs:
SingleTrackPhysics.max_steer_angleSingleTrackPhysics.max_steer_rate
11.2 Dynamics propagation¶
Arc-length segments are converted to physical segment travel times:
[
\Delta t_i = \max\left(\frac{\Delta s_i}{\max(|v_i|,\varepsilon_v)},\; \Delta t_{\min}\right).
]
max_time_step is then applied as an integration-substep cap only:
[
n_i = \left\lceil \frac{\Delta t_i}{\Delta t_{\max}} \right\rceil,\quad
\delta t_i = \frac{\Delta t_i}{\max(n_i, 1)}.
]
Point-mass update: [ v_{i+1} = \operatorname{clip}(v_i + a_{x,\text{net},i}\Delta t_i, 0, v_{\max}). ]
Single-track update uses Euler or RK4 integration of: [ \dot{x} = f(x, u), ] with a 3-DOF single-track dynamic model. The state-space structure matches the single-track equations in [1, Ch. 10].
11.3 Constraint penalties and objective terms¶
The transient objective combines:
- lap-time term \(T\),
- lateral-feasibility penalty \(J_{\text{lat}}\),
- single-track tracking penalty \(J_{\text{trk}}\),
- control smoothness penalty \(J_{\text{sm}}\).
Penalty weights come from TransientNumericsConfig:
lateral_constraint_weighttracking_weightcontrol_smoothness_weight
11.4 Backend strategies¶
- NumPy: SciPy optimizer with transient simulation core.
- Numba: numba backend dispatch with shared transient core semantics.
- Torch: differentiable transient graph plus torch optimizer and
torchdiffeq.
All modes are accessed through the same public runner:
result = simulate_lap(track=track, model=model, config=config)
Transient mode adds time/state/control traces in LapResult:
timevx,vy,yaw_ratesteer_cmd,ax_cmd
11.5 PID Gain Scheduling¶
The default transient driver is PID. ApexSim supports optional speed-dependent gain scheduling with piecewise-linear (PWL) tables: [ k(v) = \operatorname{interp}(v;\, v_{\text{nodes}}, k_{\text{nodes}}) ] with boundary clamping at the first/last node.
Scheduling modes in TransientNumericsConfig:
off: scalar gains only (legacy-compatible behavior).physics_informed: deterministic schedule generated from vehicle physics.custom: user-providedTransientPidGainSchedulingConfig.
11.6 Optimal-Control Quality Gate¶
For driver_model="optimal_control", ApexSim applies a hard fail-fast policy:
- if the optimizer does not converge,
simulate_lap(...)raisesConfigurationError(no silent fallback), - if the final transient profile is non-finite,
simulate_lap(...)raisesConfigurationError.
Validation is additionally enforced by regression tests that require OC to match quasi-static reference behavior on simple straight/circle scenarios within the same consistency thresholds used for transient PID checks.
Physics-informed longitudinal scaling at node \(v_j\): [ a_+(v_j) = a_{x,\max}(v_j, a_y=0, \theta=0, \beta=0), \quad a_-(v_j) = a_{x,\min}(v_j, a_y=0, \theta=0, \beta=0), ] [ a_{\text{eff}}(v_j) = \frac{a_+(v_j) + a_-(v_j)}{2}, \quad s_a(v_j) = \frac{a_{\text{eff}}(v_j)}{a_{\text{eff}}(v_{\text{ref}})}. ] The scheduled gains are: [ k_{p,\text{long}}(v_j) = k_{p,0}\,\operatorname{clip}(s_a, s_{\min}, s_{\max}), ] [ k_{i,\text{long}}(v_j) = k_{i,0}\,\operatorname{clip}(s_a, s_{\min}, s_{\max}), ] [ k_{d,\text{long}}(v_j) = k_{d,0}\,\operatorname{clip}(\sqrt{s_a}, s_{d,\min}, s_{d,\max}). ]
Single-track steering scaling uses speed normalization: [ s_v(v_j) = \frac{v_{\text{ref}}}{\max(v_j, v_{\min})}. ] This yields: [ k_{p,\delta}(v_j) = k_{p,0}\,\operatorname{clip}(s_v, s_{kp,\min}, s_{kp,\max}), ] [ k_{i,\delta}(v_j) = k_{i,0}\,\operatorname{clip}(s_v, s_{ki,\min}, s_{ki,\max}), ] [ k_{d,\delta}(v_j) = k_{d,0}\,\operatorname{clip}(\sqrt{s_v}, s_{kd,\min}, s_{kd,\max}), ] [ k_{v_y}(v_j) = k_{v_y,0}\,\operatorname{clip}!\left( 1 + c_{v_y}\frac{v_j}{v_{\text{ref}}}, s_{vy,\min}, s_{vy,\max} \right). ]
Default node set: [ v_{\text{nodes}} = (0,\,10,\,20,\,35,\,55,\,v_{\max})\ \text{m/s} ] (intermediate nodes above \(v_{\max}\) are omitted).
Rationale:
- Longitudinal gains rise with available traction/braking authority.
- Steering gains decrease with speed because yaw response sensitivity grows.
- Lateral-velocity damping increases with speed to stabilize transient sideslip.
References¶
[1] D. Schramm, M. Hiller, and R. Bardini, Vehicle Dynamics: Modeling and Simulation. Berlin, Heidelberg: Springer, 2014.
[2] H. B. Pacejka, Tyre and Vehicle Dynamics, 2nd ed. Oxford: Butterworth-Heinemann, 2006.
[3] W. F. Milliken and D. L. Milliken, Race Car Vehicle Dynamics. Warrendale, PA: Society of Automotive Engineers, 1995.