Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
faaf6ae5fThis file is pinned to the selected release. Download the original to open it in your design tool.
# Copyright 2026 DeepMind Technologies Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Covariance and correlation matrix report sections."""
from collections.abc import Callable
from typing import Any
import matplotlib
from matplotlib import cm
from matplotlib import colors as mpl_colors
from mujoco.sysid._src import parameter
from mujoco.sysid.report.sections.base import ReportSection
from mujoco.sysid.report.utils import get_text_color
import numpy as np
from numpy import typing as npt
def _compute_correlation(cov: npt.ArrayLike) -> np.ndarray:
"""Calculates the correlation matrix from a covariance matrix.
Formula: A[i,j] = cov[i,j] / sqrt(cov[i,i] * cov[j,j])
Args:
cov: The covariance matrix.
Returns:
The correlation matrix.
"""
cov = np.array(cov)
sqrt_diag_cov = np.sqrt(np.diag(cov))
# denom[i, j] = sqrt_diag_cov[i] * sqrt_diag_cov[j]
denom = np.outer(sqrt_diag_cov, sqrt_diag_cov)
# Safe division (handles division by zero by returning 0)
return np.divide(
cov, denom, out=np.zeros_like(cov, dtype=float), where=denom != 0
)
class Covariance(ReportSection):
"""Displays values of the covariance and correlation matrices.
Assumed to be symmetric.
"""
def __init__(
self,
title: str,
covariance: npt.ArrayLike,
parameter_dict: parameter.ParameterDict,
anchor: str = "",
collapsible: bool = True,
):
super().__init__(collapsible=collapsible)
self._title = title
self._anchor = anchor
self._covariance = np.array(covariance)
self._parameter_dict = parameter_dict
@property
def template_filename(self) -> str:
return "covariance.html"
@property
def title(self):
return self._title
@property
def anchor(self) -> str:
return self._anchor
def get_context(self) -> dict[str, Any]:
dim_names = []
for param_name, param in self._parameter_dict.parameters.items():
if param.frozen:
continue
if param.size == 1:
dim_names.append(param_name)
else:
for i in range(param.size):
dim_names.append(f"{param_name}[{i}]")
context = {
"title": self._title,
"covariance_data": self._covariance_table_data(),
"correlation_data": self._correlation_table_data(),
"dim_names": dim_names,
}
if self._covariance.size == 0:
context["message"] = (
"Covariance matrix is empty. This usually means there are no"
" parameters to optimize or all parameters are frozen."
)
return context
def header_includes(self) -> set[str]:
return {""}
def _create_table_data(
self,
matrix: np.ndarray,
norm: mpl_colors.Normalize,
cmap: mpl_colors.Colormap,
# Function to get the value used for coloring based on (row, col, value)
get_color_input_value: Callable[[int, int, float], float],
) -> list[list[dict[str, Any]]]:
"""Helper method to generate formatted table data with colors."""
scalar_map = cm.ScalarMappable(norm=norm, cmap=cmap)
table_data = []
for i in range(matrix.shape[0]):
row_data = []
for j in range(matrix.shape[1]):
value = matrix[i, j]
color_value = get_color_input_value(i, j, value)
# Get RGBA color, convert to HEX.
rgba_color = scalar_map.to_rgba(color_value) # pyright: ignore[reportArgumentType] # type: ignore[arg-type]
hex_color = mpl_colors.rgb2hex(rgba_color) # pyright: ignore[reportArgumentType] # type: ignore[arg-type]
# Make sure the text is visible over the cell color.
text_color = get_text_color(hex_color)
row_data.append({
"value": value,
"bgcolor": hex_color,
"textcolor": text_color,
})
table_data.append(row_data)
return table_data
def _covariance_table_data(self):
# Use the positive side of bwr that goes from white to red
cmap = matplotlib.colormaps["bwr"]
if self._covariance.size == 0:
max_abs_val = 1.0 # Default value to avoid errors
else:
max_abs_val = np.max(np.abs(self._covariance))
if max_abs_val == 0:
max_abs_val = 1
norm = mpl_colors.Normalize(vmin=-max_abs_val, vmax=max_abs_val)
# Color based on the actual value
def get_color_input(_i, _j, val):
return np.abs(val)
return self._create_table_data(
self._covariance, norm, cmap, get_color_input
)
def _correlation_table_data(self):
correlation = _compute_correlation(self._covariance)
# Use the positive side of bwr that goes from white to red
cmap = matplotlib.colormaps["bwr"]
norm = mpl_colors.Normalize(vmin=-1, vmax=1)
# Color based on the abs value, but use 0 for the diagonal (white)
def get_color_input(i, j, val):
if i == j:
return 0.0
return np.abs(val)
return self._create_table_data(correlation, norm, cmap, get_color_input)