Merge pull request #3079 from aftersomemath:sysid-pr

PiperOrigin-RevId: 868229512
Change-Id: I790bc08fc8b0745583a2f92d9ee2c5a19ba558ea
This commit is contained in:
Copybara-Service
2026-02-10 11:04:12 -08:00
49 changed files with 11554 additions and 0 deletions
@@ -0,0 +1,204 @@
<style>
.cov-and-corr {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 2rem;
justify-content: flex-start;
margin-bottom: 1rem;
}
/* Container for a single matrix (covariance or correlation) */
.cov-matrix-container {
border: 1px solid var(--border-color);
padding: 1rem;
border-radius: 5px;
background-color: var(--card-bg);
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.05);
max-width: 100%;
overflow-x: auto;
}
.cov-matrix-container h4 {
margin-top: 0;
margin-bottom: 0.8rem;
text-align: left;
color: var(--heading-color);
font-weight: 600;
font-size: 1.1rem;
}
.cov-matrix-container p.explanation {
font-size: 0.85rem;
color: var(--text-muted);
margin-top: 1rem;
max-width: 600px;
line-height: 1.4;
text-align: left;
}
.cov-matrix-container p.explanation strong {
color: var(--text-color);
}
table.cov {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
font-size: 0.9rem;
color: var(--text-color);
text-wrap: nowrap;
margin-left: 0;
}
table.cov tbody tr td {
padding: 0 4px;
text-align: left;
border-top: 1px solid var(--border-color);
vertical-align: middle;
/* Ensure text is readable on colored backgrounds - might need text shadow or specific color logic in python generation */
}
table.cov thead,
table.cov thead th {
background-color: var(--bg-color);
/* Very light grey header background */
color: var(--heading-color);
font-weight: 600;
border-top: none;
border-bottom: 2px solid var(--border-color);
/* Keep a stronger bottom border for header */
text-align: right;
}
/* Body row styles */
table.cov tbody tr {
background-color: var(--card-bg);
}
table.cov {
border-collapse: collapse;
margin: auto;
margin-bottom: 1rem;
}
table.cov th,
table.cov td {
border: 1px solid var(--border-color);
padding: 0 4px;
/* Very compact */
text-align: center;
font-size: 0.9em;
}
/* Row Headers (first cell in each body row) */
table.cov tbody th {
background-color: var(--bg-color);
font-weight: bold;
text-align: left;
padding-left: 10px;
position: sticky;
/* Keep row headers visible */
left: 0;
z-index: 1;
color: var(--heading-color);
}
/* Top-left corner cell */
table.cov thead th:first-child {
background-color: var(--border-color);
/* Slightly darker than bg-color */
z-index: 3;
/* Ensure it's above row/col headers */
position: sticky;
left: 0;
/* Also sticky */
}
table.cov td span {
display: inline-block;
min-width: 100%;
}
table.cov td.blank {
border: 0;
background-color: var(--card-bg);
}
</style>
<div class="cov-and-corr">
{% if message %}
<div class="cov-matrix-container" style="width: 100%;">
<h4>Covariance & Correlation</h4>
<p class="explanation" style="color: var(--text-color);">
{{ message }}
</p>
</div>
{% else %}
<div class="cov-matrix-container">
<h4>Covariance Matrix</h4>
<table class="cov">
<tbody>
{% for row in covariance_data %}
{% set outer_loop = loop %}
<tr>
<th>{{dim_names[outer_loop.index0] }}</th>
{% for cell in row %}
{%if loop.index0 <= outer_loop.index0 %} <td
style="background-color: {{ cell.bgcolor }}; color: {{ cell.textcolor }};" title="{{ cell.value }}" {%if
outer_loop.index0==loop.index0 %}class="diag" {% endif %}>
<span>
{{ "%.1e" | format(cell.value) }}
</span>
</td>
{% else %}
<td class="blank" />
{% endif %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
<p class="explanation">
The covariance matrix shows how pairs of parameters vary together.
The diagonal elements (Variance) show the variance of each parameter (how much it varies on its own).
Larger positive values indicate the parameter estimate is less certain.
The off-diagonal elements (Covariance) show the covariance between pairs of parameters, better viewed in the
normalized correlation matrix.
</p>
</div> {# End cov-matrix-container for covariance #}
<div class="cov-matrix-container">
<h4>Correlation Matrix</h4>
<table class="cov">
<tbody>
{% for row in correlation_data %}
{% set outer_loop = loop %}
<tr>
<th>{{ dim_names[outer_loop.index0] }}</th>
{% for cell in row %}
{%if loop.index0 <= outer_loop.index0 %} <td
style="background-color: {{ cell.bgcolor }}; color: {{ cell.textcolor }};" title="{{ cell.value }}" {%if
outer_loop.index0==loop.index0 %}class="diag" {% endif %}>
<span>
{{ "%.2f" | format(cell.value) }}
</span>
</td>
{% else %}
<td class="blank" />
{% endif %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
<p class="explanation">
The correlation matrix is a normalized version of the covariance matrix.
Values range from -1 to +1.
The diagonal elements are always 1, representing perfect correlation of a parameter with itself.
Off-diagonal elements (Correlation Coefficient) show the linear correlation between pairs of parameters.
+1 indicates perfect positive correlation, -1 indicates perfect negative correlation, and 0 indicates no linear
correlation.
Values close to +1 or -1 suggests that the parameters are highly dependent. Thus, if their variance is also high,
their confidence intervals will be large.
</p>
</div>
{% endif %}
</div>
@@ -0,0 +1,13 @@
<div class="group-section">
{% for child in child_sections %}
<details class="group-item" open
style="margin-bottom: 1rem; border: 1px solid var(--border-color); border-radius: 4px; padding: 1rem;">
<summary style="font-size: 1.2rem; font-weight: 600; margin-bottom: 0.5rem; border-bottom: none;">
{{ child.title }}
</summary>
<div class="group-content">
{{ child.content | safe }}
</div>
</details>
{% endfor %}
</div>
@@ -0,0 +1,90 @@
<div class="insights-container">
{% for insight in insights %}
<div class="insight-card insight-{{ insight.type }}">
<div class="insight-icon">
{% if insight.type == 'warning' %}
⚠️
{% elif insight.type == 'success' %}
✅
{% else %}
ℹ️
{% endif %}
</div>
<div class="insight-content">
<span class="insight-message">
<strong>{{ insight.title }}:</strong> {{ insight.message | safe }}
</span>
</div>
</div>
{% endfor %}
</div>
<style>
.insights-container {
display: flex;
flex-direction: column;
gap: 0.25rem;
/* Reduced gap */
}
.insight-card {
display: flex;
align-items: center;
/* Center Vertically */
padding: 0.25rem 0.5rem;
/* Smaller padding */
border-radius: 0.25rem;
border: 1px solid transparent;
background-color: var(--bg-color);
font-size: 0.9rem;
/* Smaller font */
min-height: 1.5rem;
/* Ensure roughly one line height */
}
.insight-icon {
font-size: 1rem;
margin-right: 0.5rem;
flex-shrink: 0;
line-height: 1;
}
.insight-content {
flex-grow: 1;
display: flex;
align-items: center;
}
.insight-message {
margin: 0;
color: var(--text-color);
/* Use standard text color */
white-space: nowrap;
/* Keep on one line if possible, or truncate? User said 'could just be one lines height' */
overflow: hidden;
text-overflow: ellipsis;
}
/* Allow wrapping if very long but keeping compact verticality */
.insight-message {
white-space: normal;
}
/* Types */
.insight-warning {
background-color: rgba(255, 193, 7, 0.1);
border-color: rgba(255, 193, 7, 0.4);
}
[data-theme="dark"] .insight-warning {
background-color: rgba(255, 193, 7, 0.05);
/* slightly lighter in dark mode */
}
.insight-success {
background-color: rgba(25, 135, 84, 0.1);
border-color: rgba(25, 135, 84, 0.4);
}
</style>
@@ -0,0 +1,699 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ report_title }}</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&family=JetBrains+Mono:wght@400;700&display=swap');
:root {
/* Light Theme Defaults */
--bg-color: #f4f6f8;
--card-bg: #ffffff;
--text-color: #212529;
--text-muted: #6c757d;
--heading-color: #111;
--primary-color: #0d6efd;
--border-color: #dee2e6;
--shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
--toc-link-bg: #f4f6f8;
--toc-link-hover: #0d6efd;
--toc-link-hover-text: #fff;
/* Semantic Colors - Light Default */
--color-initial: #d32f2f;
--color-nominal: #388e3c;
--color-optimized: #1976d2;
--color-success: #2e7d32;
--color-warning: #f57c00;
--color-error: #c62828;
--color-boundary: #7b1fa2;
}
[data-theme="dark"] {
/* Dark Theme Overrides */
--bg-color: #121212;
--card-bg: #1e1e1e;
--text-color: #e0e0e0;
--text-muted: #a0a0a0;
--heading-color: #fff;
--primary-color: #6ea8fe;
--border-color: #333;
--shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.3);
--toc-link-bg: #2d2d2d;
--toc-link-hover: #6ea8fe;
--toc-link-hover-text: #121212;
/* Semantic Colors - Dark Default */
--color-initial: #ef5350;
--color-nominal: #66bb6a;
--color-optimized: #42a5f5;
--color-success: #4caf50;
--color-warning: #ff9800;
--color-error: #f44336;
--color-boundary: #ab47bc;
}
/* Colorblind-friendly overrides (applied via data-colorscheme) */
[data-colorscheme="colorblind"] {
--color-initial: #d55e00;
--color-nominal: #0072b2;
--color-optimized: #cc79a7;
--color-success: #009e73;
--color-warning: #f0e442;
--color-error: #d55e00;
--color-boundary: #cc79a7;
}
/* Semantic color helper classes for inline use */
.color-initial {
color: var(--color-initial);
}
.color-nominal {
color: var(--color-nominal);
}
.color-optimized {
color: var(--color-optimized);
}
.color-success {
color: var(--color-success);
}
.color-warning {
color: var(--color-warning);
}
.color-error {
color: var(--color-error);
}
.color-boundary {
color: var(--color-boundary);
}
* {
box-sizing: border-box;
}
body {
font-family: 'DM Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 1.6;
color: var(--text-color);
background-color: var(--bg-color);
margin: 0;
padding: 0;
-webkit-font-smoothing: antialiased;
transition: background-color 0.3s, color 0.3s;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 700;
line-height: 1.2;
color: var(--heading-color);
}
h1 {
font-size: 2.25rem;
margin-bottom: 0.5rem;
}
h2 {
font-size: 1.75rem;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 2px solid var(--bg-color);
}
h3 {
font-size: 1.5rem;
}
a {
color: var(--primary-color);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.container {
max-width: var(--max-width);
margin: 0 auto;
padding: 2rem;
}
header.report-header {
background-color: var(--card-bg);
padding: 1.5rem 0;
border-bottom: 1px solid var(--border-color);
margin-bottom: 2rem;
box-shadow: var(--shadow-sm);
transition: background-color 0.3s, border-color 0.3s;
}
header .container {
padding-top: 0;
padding-bottom: 0;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
}
.report-meta {
color: var(--text-muted);
font-size: 0.9rem;
}
/* CARD STYLES */
section {
background-color: var(--card-bg);
border-radius: 0.5rem;
box-shadow: var(--shadow-sm);
margin-bottom: 2rem;
padding: 2rem;
border: 1px solid var(--border-color);
transition: background-color 0.3s, border-color 0.3s;
overflow: hidden;
/* Prevent spillover */
}
/* Make content responsive */
section>div,
section>table,
section>.plotly-graph-div {
overflow-x: auto;
max-width: 100%;
}
/* TOC STYLES */
.toc-card {
background-color: var(--card-bg);
border-radius: 0.5rem;
padding: 1.5rem;
margin-bottom: 2rem;
border: 1px solid var(--border-color);
transition: background-color 0.3s, border-color 0.3s;
}
.toc-card h3 {
margin-top: 0;
font-size: 1.25rem;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.toc-card ul {
list-style: none;
padding-left: 0;
margin: 0;
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.toc-card li a {
display: inline-block;
padding: 0.5rem 1rem;
background: var(--toc-link-bg);
border-radius: 2rem;
font-weight: 500;
font-size: 0.9rem;
transition: all 0.2s;
color: var(--text-color);
}
.toc-card li a:hover {
background: var(--toc-link-hover);
color: var(--toc-link-hover-text);
text-decoration: none;
}
/* DETAILS / SUMMARY overrides */
details {
margin-top: 0;
border: none;
background: none;
padding: 0;
}
details>summary {
font-size: 1.75rem;
font-weight: 700;
cursor: pointer;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 2px solid var(--bg-color);
list-style: none;
display: flex;
align-items: center;
color: var(--heading-color);
}
details>summary::-webkit-details-marker {
display: none;
}
details>summary::after {
content: '+';
margin-left: auto;
font-weight: 400;
color: var(--text-muted);
}
details[open]>summary::after {
content: '−';
}
/* Code/Mono font improvements */
code,
pre {
font-family: 'JetBrains Mono', source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
}
/* Toggle Button */
#theme-toggle,
#colorscheme-toggle {
background: none;
border: 1px solid var(--border-color);
border-radius: 2rem;
padding: 0.5rem 1rem;
cursor: pointer;
font-family: inherit;
font-size: 0.9rem;
color: var(--text-color);
display: flex;
align-items: center;
gap: 0.5rem;
}
#theme-toggle:hover,
#colorscheme-toggle:hover {
background-color: var(--bg-color);
}
/* Header Controls */
.header-controls {
display: flex;
gap: 0.5rem;
align-items: center;
}
/* Color Scheme Dropdown */
.color-scheme-dropdown {
position: relative;
}
.color-panel {
position: absolute;
top: calc(100% + 0.5rem);
right: 0;
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 0.75rem;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
padding: 1rem;
z-index: 1000;
min-width: 220px;
opacity: 0;
transform: translateY(-10px) scale(0.95);
pointer-events: none;
transition: opacity 0.2s ease, transform 0.2s ease;
}
.color-panel.visible {
opacity: 1;
transform: translateY(0) scale(1);
pointer-events: auto;
}
.color-panel-title {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-muted);
margin-bottom: 0.75rem;
}
.color-option {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.625rem 0.75rem;
border: none;
background: none;
border-radius: 0.5rem;
cursor: pointer;
width: 100%;
text-align: left;
color: var(--text-color);
font-family: inherit;
font-size: 0.9rem;
transition: background-color 0.15s ease;
}
.color-option:hover {
background-color: var(--bg-color);
}
.color-option.active {
background-color: var(--primary-color);
color: white;
}
.color-swatch {
display: flex;
gap: 3px;
}
.color-swatch span {
width: 14px;
height: 14px;
border-radius: 3px;
}
.swatch-default .c1 {
background: #ef5350;
}
.swatch-default .c2 {
background: #66bb6a;
}
.swatch-default .c3 {
background: #42a5f5;
}
.swatch-colorblind .c1 {
background: #d55e00;
}
.swatch-colorblind .c2 {
background: #0072b2;
}
.swatch-colorblind .c3 {
background: #cc79a7;
}
/* Back to Top */
#back-to-top {
position: fixed;
bottom: 2rem;
right: 2rem;
background-color: var(--primary-color);
color: #fff;
border: none;
border-radius: 50%;
width: 3rem;
height: 3rem;
display: none;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: var(--shadow-md);
z-index: 1000;
font-size: 1.5rem;
transition: transform 0.2s;
}
#back-to-top:hover {
transform: translateY(-3px);
}
/* Responsive */
@media (max-width: 768px) {
.container {
padding: 1rem;
}
h1 {
font-size: 1.75rem;
}
section {
padding: 1.5rem;
}
}
</style>
{% for include in header_includes %}
{{ include | safe }}
{% endfor %}
</head>
<body>
<header class="report-header">
<div class="container">
<div>
<h1>{{ report_title }}</h1>
{% if report_timestamp %}
<div class="report-meta">Generated at {{ report_timestamp }}</div>
{% endif %}
</div>
<div class="header-controls">
<button id="theme-toggle" onclick="toggleTheme()">
<span>🌗</span> Theme
</button>
<div class="color-scheme-dropdown">
<button id="colorscheme-toggle" onclick="toggleColorSchemePanel()">
<span>🎨</span> Colors
</button>
<div id="color-panel" class="color-panel">
<div class="color-panel-title">Color Scheme</div>
<button class="color-option active" data-scheme="default" onclick="setColorScheme('default')">
<div class="color-swatch swatch-default">
<span class="c1"></span>
<span class="c2"></span>
<span class="c3"></span>
</div>
Default
</button>
<button class="color-option" data-scheme="colorblind" onclick="setColorScheme('colorblind')">
<div class="color-swatch swatch-colorblind">
<span class="c1"></span>
<span class="c2"></span>
<span class="c3"></span>
</div>
Colorblind Safe
</button>
</div>
</div>
</div>
</div>
</header>
<div class="container">
<nav class="toc-card">
<h3>Contents</h3>
<ul>
{% for sec in sections %}
<li><a href="#{{ sec.anchor }}">{{ sec.title }}</a></li>
{% endfor %}
</ul>
</nav>
<main>
{% for sec in sections %}
<section id="{{ sec.anchor }}">
{% if sec.collapsible %}
<details {% if sec.is_open %}open{% endif %}>
<summary>{{ sec.title }}</summary>
{{ sec.content | safe }}
</details>
{% else %}
<h2>{{ sec.title }}</h2>
{{ sec.content | safe }}
{% endif %}
</section>
{% endfor %}
</main>
</div>
<button id="back-to-top" onclick="window.scrollTo({top: 0, behavior: 'smooth'})" title="Back to Top">
↑
</button>
<script>
// Theme Logic
function toggleTheme() {
const body = document.body;
const currentTheme = body.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
body.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
updatePlotsTheme(newTheme);
}
// Update Plotly plots
function updatePlotsTheme(theme) {
const isDark = theme === 'dark';
const bgColor = isDark ? '#1e1e1e' : '#ffffff';
const fontColor = isDark ? '#e0e0e0' : '#212529';
const gridColor = isDark ? '#444' : 'rgba(211, 211, 211, 0.7)';
const lineColor = isDark ? '#666' : 'black';
const update = {
'paper_bgcolor': bgColor,
'plot_bgcolor': bgColor,
'font.color': fontColor
};
const plots = document.getElementsByClassName('plotly-graph-div');
for (let i = 0; i < plots.length; i++) {
const div = plots[i];
// dynamic update object
const layoutUpdate = { ...update };
// Iterate over layout keys to find axes
if (div.layout) {
Object.keys(div.layout).forEach(key => {
if (key.match(/^[xy]axis\d*$/)) {
layoutUpdate[`${key}.gridcolor`] = gridColor;
layoutUpdate[`${key}.linecolor`] = lineColor;
layoutUpdate[`${key}.zerolinecolor`] = gridColor;
}
});
}
Plotly.relayout(div, layoutUpdate);
}
}
// Load saved theme
const savedTheme = localStorage.getItem('theme');
let startTheme = 'light';
if (savedTheme) {
document.body.setAttribute('data-theme', savedTheme);
startTheme = savedTheme;
} else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.body.setAttribute('data-theme', 'dark');
startTheme = 'dark';
}
// Color Scheme Logic
function toggleColorSchemePanel() {
const panel = document.getElementById('color-panel');
panel.classList.toggle('visible');
}
function setColorScheme(scheme) {
// Update body attribute
if (scheme === 'colorblind') {
document.body.setAttribute('data-colorscheme', 'colorblind');
} else {
document.body.removeAttribute('data-colorscheme');
}
localStorage.setItem('colorscheme', scheme);
// Update active state on buttons
document.querySelectorAll('.color-option').forEach(btn => {
btn.classList.toggle('active', btn.dataset.scheme === scheme);
});
// Close panel
document.getElementById('color-panel').classList.remove('visible');
}
// Close panel when clicking outside
document.addEventListener('click', (e) => {
const dropdown = document.querySelector('.color-scheme-dropdown');
if (dropdown && !dropdown.contains(e.target)) {
document.getElementById('color-panel').classList.remove('visible');
}
});
// Load saved color scheme
const savedColorScheme = localStorage.getItem('colorscheme');
if (savedColorScheme === 'colorblind') {
document.body.setAttribute('data-colorscheme', 'colorblind');
document.querySelectorAll('.color-option').forEach(btn => {
btn.classList.toggle('active', btn.dataset.scheme === 'colorblind');
});
}
// Apply plot theme on load (wait for plotly to init?)
// Plotly scripts usually run inline, so we might need to wait a tick or use an event.
// However, the report generates static HTML where the Plotly.newPlot is called in script tags.
// Those script tags might run after this one depending on placement.
// layout.html ends with this script, and content is inserted before it?
// StartTheme application:
window.addEventListener('DOMContentLoaded', () => {
// Observers to catch plots as they load if they are async,
// but typically jinja renders them inline.
// We'll try running it immediately and also after a short delay to be safe.
updatePlotsTheme(startTheme);
setTimeout(() => updatePlotsTheme(startTheme), 500);
});
// Back to Top Logic
const backToTopBtn = document.getElementById('back-to-top');
window.addEventListener('scroll', () => {
if (window.scrollY > 300) {
backToTopBtn.style.display = 'flex';
} else {
backToTopBtn.style.display = 'none';
}
});
// Video Synchronization
const videos = document.querySelectorAll('video');
let ignoreEvents = false;
videos.forEach(video => {
video.addEventListener('play', (e) => {
if (ignoreEvents) return;
ignoreEvents = true;
videos.forEach(v => {
if (v !== e.target && v.paused) {
v.play();
}
});
setTimeout(() => ignoreEvents = false, 10);
});
video.addEventListener('pause', (e) => {
if (ignoreEvents) return;
ignoreEvents = true;
videos.forEach(v => {
if (v !== e.target && !v.paused) {
v.pause();
}
});
setTimeout(() => ignoreEvents = false, 10);
});
video.addEventListener('seeking', (e) => {
if (ignoreEvents) return;
ignoreEvents = true;
const currentTime = e.target.currentTime;
videos.forEach(v => {
if (v !== e.target && Math.abs(v.currentTime - currentTime) > 0.05) {
v.currentTime = currentTime;
}
});
setTimeout(() => ignoreEvents = false, 10);
});
});
</script>
</body>
</html>
@@ -0,0 +1,20 @@
{% if objective_plot_html %}
<h3>Objective Function</h3>
<div>
{{ objective_plot_html | safe }}
</div>
{% endif %}
{% if candidate_heatmap_html %}
<h3>Parameter Candidate Heatmap</h3>
<div>
{{ candidate_heatmap_html | safe }}
</div>
{% endif %}
{% if candidate_plots_html %}
<h3>Parameter Candidates</h3>
{% for plot_html in candidate_plots_html %}
<div>
{{ plot_html | safe }}
</div>
{% endfor %}
{% endif %}
@@ -0,0 +1,3 @@
<div>
{{ plot_html | safe }}
</div>
@@ -0,0 +1,147 @@
<style>
table.pt {
width: 100%;
border-collapse: collapse;
margin-bottom: 1rem;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
font-size: 0.9rem;
color: var(--text-color);
}
table.pt thead tr th,
table.pt tbody tr td {
padding: 10px 12px;
text-align: left;
border-top: 1px solid var(--border-color);
vertical-align: middle;
}
table.pt thead,
table.pt thead th {
background-color: var(--bg-color);
/* Very light grey header background */
color: var(--heading-color);
font-weight: 600;
border-top: none;
border-bottom: 2px solid var(--border-color);
/* Keep a stronger bottom border for header */
text-align: right;
}
table.pt tbody tr:first-child td {
border-top: none;
}
table.pt th,
table.pt td {
border-left: 1px solid var(--border-color);
border-right: 1px solid var(--border-color);
border-bottom: 1px solid var(--border-color);
}
/* Body row styles */
table.pt tbody tr {
background-color: var(--card-bg);
}
/* Alternating row colors (need a variable or opacity) */
/* Using a slight transparency for alternating rows to adapt to dark/light */
table.pt tbody tr:nth-child(even) {
background-color: rgba(128, 128, 128, 0.05);
}
table.pt tbody tr:hover {
background-color: rgba(128, 128, 128, 0.1);
}
table.pt thead th:first-child {
text-align: left;
/* Align 'Parameter' header left */
}
table.pt tbody tr td {
text-align: right;
font-family: "SFMono-Regular", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}
table.pt tbody tr td.pt_param {
font-weight: 500;
white-space: nowrap;
text-align: left;
color: var(--primary-color);
}
/* Update these to use variable or standard colors suitable for both modes */
.pt_param {
color: var(--primary-color);
}
.pt_nominal {
color: var(--primary-color);
/* Was #1f1fb4 */
}
.pt_bound {
color: var(--primary-color);
}
.pt_small_error {
color: var(--color-success);
}
.pt_medium_error {
color: var(--color-warning);
}
.pt_large_error {
color: var(--color-error);
}
.pt_on_boundary {
color: var(--color-boundary);
}
</style>
<div class="parameters">
<table class="pt">
<thead>
<tr>
{% for header in headers %}
<th>{{ header }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in table_data %}
<tr>
<td class="pt_param" {% if row.is_frozen %}style="color: grey;" {% endif %}>{{ row.name }}</td> {# Parameter
name #}
{% if row.initial is defined %}
<td class="pt_nominal" style="color: var(--text-muted);">{{ "%.4f" | format(row.initial) }}</td>
{% endif %}
{% if row.nominal is defined %}
<td class="pt_nominal">{{ "%.4f" | format(row.nominal) }}</td>
{% endif %}
<td class="{{row.error_class}}">{{ "%.4f" | format(row.pred) }}</td>
{% if row.is_frozen %}
<td class="pt_bound">-</td>
<td class="pt_bound">-</td>
{% else %}
<td class="pt_bound">{{ "%.4f" | format(row.lower_bound) }}</td>
<td class="pt_bound">{{ "%.4f" | format(row.upper_bound) }}</td>
{% endif %}
{% if row.abs_err is defined %}
<td class="{{row.error_class}}">{{ "%.4f" | format(row.abs_err) }}</td>
{% endif %}
{# Format relative error as percentage #}
{% if row.rel_err is defined %}
<td class="{{row.error_class}}">{{ "%.1f%%" | format(row.rel_err * 100) }}</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
{% if overall_rmse is not none %}
<p class="pt_mse">Overall Parameters RMSE: {{ "%.4f" | format(overall_rmse) }}</p>
{% endif %}
<p style="color: grey; font-size: 0.85em; margin-top: 0.5rem;">* parameter is frozen</p>
</div>
@@ -0,0 +1,8 @@
<div class="plot-container">
{{ plot_div | safe }}
{% if caption %}
<div class="plot-caption" style="margin-top: 0.5rem; color: var(--text-muted); font-size: 0.9em;">
<em>{{ caption | safe }}</em>
</div>
{% endif %}
</div>
@@ -0,0 +1,13 @@
<div class="row-section">
{% if description %}
<p class="section-description" style="color: var(--text-muted); margin-bottom: 1rem;">{{ description | safe }}</p>
{% endif %}
<div class="row-content"
style="display: flex; flex-direction: row; flex-wrap: wrap; gap: 1rem; justify-content: space-around;">
{% for child in child_sections %}
<div class="row-item" style="flex: 1; min-width: 300px;">
{{ child.content | safe }}
</div>
{% endfor %}
</div>
</div>
@@ -0,0 +1,3 @@
<div class="sensor-comparison-section">
{{ plot_div | safe }}
</div>
@@ -0,0 +1,11 @@
<div style="text-align: center; margin: 20px 0;">
<video src="{{ video_filepath }}" width="{{ width }}" height="{{ height }}" {{ controls }} {{ autoplay }} {{ muted
}} {{ loop }} preload="metadata" style="max-width: 100%; height: auto;">
Your browser does not support the video tag.
<a href="{{ video_filepath }}" target="_blank">Download the video here.</a>
</video>
{% if caption %}
<p style="margin-top: 10px; font-size: 0.9em; color: var(--text-muted);" class="video-caption">{{ caption | safe }}
</p>
{% endif %}
</div>