from typing import Any from mujoco.sysid._src import parameter from mujoco.sysid.report.sections.base import ReportSection class AutomatedInsights(ReportSection): """ Analyzes identification results and generates automated insights/suggestions. Currently checks for parameters stuck at boundaries. """ def __init__( self, title: str, parameter_dict: parameter.ParameterDict, anchor: str = "insights", collapsible: bool = True, ): # Default to collapsed super().__init__(collapsible=collapsible, is_open=False) self._anchor = anchor self._parameter_dict = parameter_dict self._x_hat = parameter_dict.as_vector() self._insights = self._generate_insights() self._n_warnings = len([i for i in self._insights if i["type"] == "warning"]) self._title = f"Log - {self._n_warnings} warnings" def _generate_insights(self) -> list[dict[str, Any]]: insights = [] # Logic: Check for boundary hits param_names = self._parameter_dict.get_non_frozen_parameter_names() bounds = self._parameter_dict.get_bounds() lower_bounds, upper_bounds = bounds if len(self._x_hat) != len(param_names): raise ValueError("Parameter count mismatch") for i, name in enumerate(param_names): val = self._x_hat[i] lb = lower_bounds[i] ub = upper_bounds[i] rng = max(ub - lb, 1e-9) # Threshold: 0.1% of range or 1e-6 absolute threshold = rng * 1e-3 if threshold < 1e-8: threshold = 1e-8 if abs(val - lb) < threshold: insights.append( { "type": "warning", "title": "Lower Bound Hit", "message": f"Parameter {name} ({val:.4g}) is at its lower bound ({lb:.4g}).", } ) elif abs(val - ub) < threshold: insights.append( { "type": "warning", "title": "Upper Bound Hit", "message": f"Parameter {name} ({val:.4g}) is at its upper bound ({ub:.4g}).", } ) if not insights: insights.append( { "type": "success", "title": "No Issues Detected", "message": "All parameters are within their bounds.", } ) return insights @property def template_filename(self) -> str: return "insights.html" @property def title(self) -> str: return self._title @property def anchor(self) -> str: return self._anchor def get_context(self) -> dict[str, Any]: return {"title": self._title, "insights": self._insights}