3ec09f7296
PiperOrigin-RevId: 868229512 Change-Id: I790bc08fc8b0745583a2f92d9ee2c5a19ba558ea
178 lines
5.4 KiB
Python
178 lines
5.4 KiB
Python
# 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)
|