suboptimumg.log_analysis.yaw_fit#
- suboptimumg.log_analysis.yaw_fit.apply_relaxation_lag(u_rad, V, t, L_f)[source]#
Lag the steering input by a first-order LPV filter,
tau(V) = L_f / V.Approximates front-tire relaxation by lagging the input rather than the full kinematic slip angle: exact at low frequency, slightly off at high frequency because the body terms (beta, lf*r/V) are not lagged. A cheap pre-filter that captures relaxation length’s dominant phase effect without a state-space rewrite.
L_f <= 0is a no-op.- Parameters:
u_rad (NDArray[np.float64]) – Steering input (rad).
V (NDArray[np.float64]) – Speed at each sample (m/s); clipped at
V_FLOOR_MPS.t (NDArray[np.float64]) – Uniformly-sampled time vector (s).
L_f (float) – Front relaxation length (m).
- Returns:
Lagged input, same length as
u_rad.- Return type:
NDArray[np.float64]
- suboptimumg.log_analysis.yaw_fit.axle_slip_angles(w, irl_car)[source]#
Per-sample front and rear slip-angle magnitudes (rad).
|alpha_f| = |delta - (beta + lf r / V)|and|alpha_r| = |-beta + lr r / V|. Both derive purely from measured signals, so the optimizer sees them as a fixed time-series – there is no feedback through the simulated state during fitting.- Parameters:
w (WindowArrays) – Must carry both
body_slip_radandbicycle_steer_rad.irl_car (IrlCar) – Source of the wheelbase and CG split.
- Returns:
(|alpha_f|, |alpha_r|), each the length ofw.t.- Return type:
tuple[NDArray[np.float64], NDArray[np.float64]]
- Raises:
KeyError – If the body-slip or bicycle-steer channel is absent from
w.
- suboptimumg.log_analysis.yaw_fit.bicycle_coeffs(V, chassis, irl_car, Ca_f=None, Ca_r=None)[source]#
Linear-bicycle (K, T_z, omega_n, zeta) at each speed. Vectorised over
V.- Parameters:
V (NDArray[np.float64]) – Speed(s) (m/s); must all be strictly positive.
chassis (ChassisParams) – The three chassis scalars.
Izzis always taken from here.irl_car (IrlCar) – Source of the mass, wheelbase, and CG split.
Ca_f (NDArray[np.float64], optional) – Per-sample cornering stiffnesses, overriding
chassis. Used by the Ca-decay scheduler, where stiffness varies sample-by-sample.Ca_r (NDArray[np.float64], optional) – Per-sample cornering stiffnesses, overriding
chassis. Used by the Ca-decay scheduler, where stiffness varies sample-by-sample.
- Returns:
Every field shaped like
V.- Return type:
Notes
These are the coefficients of the standard 2-DOF linear bicycle transfer function from front tire steer (rad) to yaw rate (rad/s):
H(s; V) = K(V) wn(V)^2 (1 + T_z(V) s) / (s^2 + 2 zeta(V) wn(V) s + wn(V)^2)
All four follow analytically from the three chassis scalars plus the known geometry, which is why those three scalars are the only thing
fit_yawoptimizes. A free-coefficient LPV polynomial was tried first and proved unidentifiable on near-limit lapping data: the optimizer slid along the K/T_z product ridge to nonsense (T_z ~ 2500 s,K -> 0) while still scoring 80-90% VAF. Tying every coefficient back to the bicycle, with hard prior bounds from the car YAML, removes that ridge.Inside the prior box
omega_n^2is positive for any sensible understeer car; the clip below only guards the extreme-oversteer combinations the optimizer may briefly probe.
- suboptimumg.log_analysis.yaw_fit.bicycle_points(chassis, irl_car, speeds)[source]#
Linear-region bicycle coefficients sampled at each of
speeds.- Parameters:
chassis (ChassisParams)
irl_car (IrlCar)
speeds (tuple[float, ...])
- Return type:
list[BicyclePoint]
- suboptimumg.log_analysis.yaw_fit.build_fit_views(window_arrays, mode)[source]#
Build the
(sub-window, sign, source id)views the residual is summed over.Lapping data is rarely symmetric – a track run one direction steers mostly one way, which lets the optimizer trade a real steering offset against a fake one. Mirroring views balances the excitation around zero.
- Parameters:
window_arrays (list of WindowArrays) – Sub-windows to build views from.
mode ({"none", "mirror_all", "flip_half"}) –
"none"uses each sub-window once, unmirrored."mirror_all"uses each one twice, at both signs."flip_half"mirrors the half of the sub-windows with the most positive mean steer.
- Return type:
list of FitView
- Raises:
ValueError – If
modeis unrecognised.
- suboptimumg.log_analysis.yaw_fit.build_param_layout(spec, irl_car, bounds_pct=None, u_off_max_deg=10.0, alpha_init=1.0, alpha_bounds=(0.0, 50.0), p_init=1.0, p_bounds=(0.5, 4.0), L_f_init=0.3, L_f_bounds=(0.05, 1.5), pin=None)[source]#
Build the parameter vector for
spec, boxed by the car’s priors.Ca_f,Ca_r,Izzandu_offare always present. A Ca scheduler addsalpha_f,alpha_r,p; front relaxation addsL_f. The three chassis priors and their bounds come from the car YAML’sdynamics_setup.- Parameters:
spec (FitSpec) – Decides which optional parameters are appended.
irl_car (IrlCar) – Source of the
dynamics_setuppriors.bounds_pct (dict[str, float], optional) – Overrides the YAML’s
prior_bounds. KeysCa_front_pct,Ca_rear_pct,Izz_pct.u_off_max_deg (float, optional) – Symmetric bound on the steering-offset parameter (deg).
alpha_init (optional) – Initial guess and bounds for the per-axle Ca-decay rates.
alpha_bounds (optional) – Initial guess and bounds for the per-axle Ca-decay rates.
p_init (optional) – Initial guess and bounds for the shared Ca-decay exponent.
p_bounds (optional) – Initial guess and bounds for the shared Ca-decay exponent.
L_f_init (optional) – Initial guess and bounds for the front relaxation length (m).
L_f_bounds (optional) – Initial guess and bounds for the front relaxation length (m).
pin (dict[str, float], optional) – Parameters to hold fixed at the given values instead of fitting.
- Return type:
- Raises:
ValueError – If
irl_carcarries nodynamics_setuppriors, orpinnames a parameter this spec does not have.
- suboptimumg.log_analysis.yaw_fit.compare_fits(fits)[source]#
Table comparing several fits, keyed by display name.
- Parameters:
fits (dict[str, YawFit])
- Return type:
- suboptimumg.log_analysis.yaw_fit.extract_window_arrays(sub, index, ctx)[source]#
Pull the aligned numpy arrays for one sub-window off a
SingleRunData.Every channel is joined onto the yaw-rate timestamp grid, so the returned arrays are equal-length regardless of residual per-channel grid differences. Angles are converted to radians per the unit fields on
ctx.- Parameters:
sub (SingleRunData) – One sub-window, as produced by
auto_segment/resample_subwindows.index (int) – Position of
subin its parent list; becomesWindowArrays.sw_id.ctx (FitContext) – Channel names and units.
- Returns:
Aligned arrays for
sub.- Return type:
- Raises:
KeyError – If a required channel is missing from
sub.
- suboptimumg.log_analysis.yaw_fit.fit_yaw(fit_views, window_arrays, spec, layout, ctx, balance_mode='none', verbose=0, max_nfev=350)[source]#
Fit
specto the sub-windows by bounded output-error least squares.The optimizer minimises the concatenated residual across
fit_views. Each view’s simulation is initialised at that view’s measured yaw rate, so the residual starts at zero and the optimizer never has to fight an initial-condition mismatch alongside the dynamics.- Parameters:
fit_views (list of FitView) – Views the residual is summed over; see
build_fit_views.window_arrays (list of WindowArrays) – Source sub-windows, re-simulated once at the fitted parameters to produce the reported predictions.
spec (FitSpec) – The variant to fit.
layout (ParamLayout) – The parameter vector and its bounds.
ctx (FitContext) – Channel/geometry metadata, recorded on the fit.
balance_mode (str, optional) – Not used here; only recorded on the fit so you can see later how the views were weighted. See
build_fit_views.verbose (optional) – Forwarded to
scipy.optimize.least_squares.max_nfev (optional) – Forwarded to
scipy.optimize.least_squares.
- Returns:
The fitted parameters and their per-sub-window diagnostics.
- Return type:
- suboptimumg.log_analysis.yaw_fit.predict_yaw(w, x, spec, layout, irl_car, y_init=None, sign=1.0)[source]#
Predict one sub-window’s yaw rate under
specat parameter vectorx.The pipeline is: offset and mirror the input (
u_eff = sign * (u - u_off)), optionally lag it for front-tire relaxation, optionally decay each axle’s cornering stiffness against its own slip angle, then run the LPV biquad.- Parameters:
w (WindowArrays) – The sub-window to predict over.
x (NDArray[np.float64]) – Free-parameter vector, aligned with
layout.free.spec (FitSpec) – Selects the Ca scheduler and relaxation flavor.
layout (ParamLayout) – Maps
x(plus any pinned values) onto named parameters.irl_car (IrlCar) – Source of the mass, wheelbase, and CG split.
y_init (float, optional) – Forced initial output; see
simulate_lpv_yaw.sign (float, optional) – Mirrors the input, for the sign-balanced fit views.
- Returns:
Predicted yaw rate (rad/s), same length as
w.t.- Return type:
NDArray[np.float64]
Notes
The two optional extensions sit on top of the linear bicycle core (see
bicycle_coeffs).ca_scheduler="abs_alpha_per_axle"decays each axle’s cornering stiffness against its own measured slip-angle magnitude:Ca_f(t) = Ca_f0 / (1 + alpha_f |alpha_f(t)|^p)
which is what lets one fit span the near-limit sub-windows where the tires have left the linear region.
relaxation="front"lags the steering input bytau(V) = L_f / V, modelling front-tire relaxation length; seeapply_relaxation_lag.
- suboptimumg.log_analysis.yaw_fit.simulate_lpv_yaw(t, u_rad, V, chassis, irl_car, Ca_f=None, Ca_r=None, y_init=None)[source]#
Sample-by-sample Tustin biquad with V(t)-scheduled coefficients.
At each sample the four LPV coefficients are evaluated at that sample’s speed (and, when the Ca-decay scheduler is active, that sample’s cornering stiffnesses), then converted to a discrete biquad by Tustin substitution
s = (2/dt)(z-1)/(z+1).- Parameters:
t (NDArray[np.float64]) – Uniformly-sampled time vector (s).
u_rad (NDArray[np.float64]) – Front tire steer input (rad).
V (NDArray[np.float64]) – Speed at each sample (m/s); clipped at
V_FLOOR_MPS.chassis (ChassisParams) – The three chassis scalars.
irl_car (IrlCar) – Source of the mass, wheelbase, and CG split.
Ca_f (NDArray[np.float64], optional) – Per-sample cornering stiffnesses; see
bicycle_coeffs.Ca_r (NDArray[np.float64], optional) – Per-sample cornering stiffnesses; see
bicycle_coeffs.y_init (float, optional) – Forced initial output (rad/s). The biquad states are set so
y[0]equals it exactly. Passing the measured yaw rate at the sub-window start is the intended path during fitting: the residual at t=0 is then zero and the optimizer does not have to fight an initial-condition mismatch on top of the dynamics. Defaults to the model’s own steady state,K(V[0]) * u[0].
- Returns:
Simulated yaw rate (rad/s), same length as
t.- Return type:
NDArray[np.float64]
- suboptimumg.log_analysis.yaw_fit.summarize_fit(fit, sample_speeds=(10.0, 15.0, 20.0))[source]#
Console summary of a fit plus its linear-region coefficients at
sample_speeds.The coefficients are the linear-region ones, i.e. evaluated with the Ca decay switched off, so they describe the car’s small-slip behaviour.
- Parameters:
fit (YawFit)
sample_speeds (tuple[float, ...])
- Return type:
str
- suboptimumg.log_analysis.yaw_fit.validate_fit(val_window_arrays, fit)[source]#
Evaluate a frozen fit on held-out sub-windows – no re-optimisation.
AIC / BIC use the fit’s own parameter count, so they are directly comparable against the training numbers on
fit.train.- Parameters:
val_window_arrays (list of WindowArrays) – Held-out sub-windows, typically from another session’s artifact.
fit (YawFit) – The frozen fit to evaluate.
- Returns:
Predictions and metrics on
val_window_arrays.- Return type:
- suboptimumg.log_analysis.yaw_fit.weighted_mean_u_deg(views)[source]#
Sample-weighted mean steering angle across
views(deg).Near zero means the views are sign-balanced; far from zero means the steering offset and the chassis parameters are fighting each other.
- Parameters:
views (list[tuple[WindowArrays, float, int]])
- Return type:
float