Source code for suboptimumg.log_analysis.yaw_segmentation_models

import numpy as np
from numpy.typing import NDArray
from pydantic import BaseModel, Field


[docs] class StationarityCell(BaseModel): """One (window length, tolerance) cell of the stationarity scan.""" window_s: float = Field(description="Sliding-window length tested (s)") tol_pct: float = Field( description="Allowed peak-to-peak speed variation, as a percent of the window mean" ) n_windows: int = Field(description="Number of sliding windows evaluated") n_passing: int = Field(description="Number of windows whose speed held within tol_pct") fraction_passing: float = Field( description="n_passing / n_windows; NaN when the window is longer than the log" )
[docs] class StationarityReport(BaseModel): """Grid of ``StationarityCell`` from ``scan_speed_stationarity``. ``fraction_passing`` near zero at every long window means the car never holds a steady speed, so a nonparametric Welch FRF is not viable and the parametric LPV output-error fit is the right path. """ speed_col: str = Field(description="Speed channel the scan was run on") overlap: float = Field(description="Sliding-window overlap fraction (0 = none, 0.5 = 50%)") cells: list[StationarityCell] = Field( description="One cell per (window length, tolerance) pair" )
[docs] def fraction_grid( self, win_lengths_s: tuple[float, ...], tol_pcts: tuple[float, ...] ) -> NDArray[np.float64]: """``(len(win_lengths_s), len(tol_pcts))`` array of ``fraction_passing``.""" lookup = {(c.window_s, c.tol_pct): c.fraction_passing for c in self.cells} return np.array([[lookup.get((w, t), np.nan) for t in tol_pcts] for w in win_lengths_s])
def __str__(self) -> str: best = max(self.cells, key=lambda c: c.fraction_passing, default=None) head = f"Stationarity scan on {self.speed_col} (overlap={self.overlap:.0%})" if best is None: return head + "\n (no windows)" return ( f"{head}\n best cell: {best.fraction_passing:.1%} of windows steady " f"at len={best.window_s:g}s, tol=+/-{best.tol_pct:g}%" )
[docs] class MaskReport(BaseModel): """Summary of how ``auto_segment`` reduced one manual window.""" manual_window: tuple[float, float] = Field( description="The operator-picked coarse window (t_start, t_end) in seconds" ) n_input_pts: int = Field(description="Samples falling inside the manual window") n_kept_pts: int = Field(description="Samples surviving the speed and slip masks") n_low_speed_pts: int = Field(description="Samples rejected for being below the speed floor") n_high_slip_pts: int = Field(description="Samples rejected for exceeding the slip ceiling") sub_windows: list[tuple[float, float]] = Field( description="Accepted sub-window (t_start, t_end) times (s)" ) rejected_short: list[tuple[float, float]] = Field( description="Surviving runs dropped for being shorter than min_window_s (s)" ) def __str__(self) -> str: w0, w1 = self.manual_window return ( f"window ({w0:.1f}, {w1:.1f})s: {len(self.sub_windows)} kept, " f"{len(self.rejected_short)} short-dropped, " f"low-speed={self.n_low_speed_pts}, high-slip={self.n_high_slip_pts}" )
[docs] class SubwindowSummary(BaseModel): """Per-sub-window quality metrics, used to triage windows before fitting.""" sub_index: int = Field(description="Position of the sub-window in its parent list") n_pts: int = Field(description="Number of samples in the sub-window") duration_s: float = Field(description="Sub-window duration (s)") fs_hz: float = Field(description="Estimated sample rate (Hz)") speed_mean: float = Field(default=float("nan"), description="Mean speed (m/s)") speed_pp_pct: float = Field( default=float("nan"), description="Peak-to-peak speed variation as a percent of the mean", ) ax_rms: float = Field(default=float("nan"), description="RMS longitudinal acceleration (m/s^2)") input_pp: float = Field( default=float("nan"), description="Peak-to-peak steering excitation (deg)" ) slip_rms: float = Field(default=float("nan"), description="RMS body slip angle (deg)") def __str__(self) -> str: return ( f"sub {self.sub_index:>2d}: {self.duration_s:5.1f}s @ {self.fs_hz:4.0f}Hz " f"V={self.speed_mean:5.1f} (+/-{self.speed_pp_pct:4.0f}%) " f"|ax|rms={self.ax_rms:4.2f} steer_pp={self.input_pp:5.1f} " f"slip_rms={self.slip_rms:4.2f}" )