feat: Implement Protocol 13 - Real-Time Dashboard Tracking

Complete implementation of Protocol 13 featuring real-time web dashboard
for monitoring multi-objective optimization studies.

## New Features

### Backend (Python)
- Real-time tracking system with per-trial JSON writes
- New API endpoints for metadata, optimizer state, and Pareto fronts
- Unit inference from objective descriptions
- Multi-objective support using Optuna's best_trials API

### Frontend (React + TypeScript)
- OptimizerPanel: Real-time optimizer state (phase, strategy, progress)
- ParetoPlot: Pareto front visualization with normalization toggle
  - 3 modes: Raw, Min-Max [0-1], Z-Score standardization
  - Pareto front line connecting optimal points
- ParallelCoordinatesPlot: High-dimensional interactive visualization
  - Objectives + design variables on parallel axes
  - Click-to-select, hover-to-highlight
  - Color-coded feasibility
- Dynamic units throughout all visualizations

### Documentation
- Comprehensive Protocol 13 guide with architecture, data flow, usage

## Files Added
- `docs/PROTOCOL_13_DASHBOARD.md`
- `atomizer-dashboard/frontend/src/components/OptimizerPanel.tsx`
- `atomizer-dashboard/frontend/src/components/ParetoPlot.tsx`
- `atomizer-dashboard/frontend/src/components/ParallelCoordinatesPlot.tsx`
- `optimization_engine/realtime_tracking.py`

## Files Modified
- `atomizer-dashboard/frontend/src/pages/Dashboard.tsx`
- `atomizer-dashboard/backend/api/routes/optimization.py`
- `optimization_engine/intelligent_optimizer.py`

## Testing
- Tested with bracket_stiffness_optimization_V2 (30 trials, 20 Pareto solutions)
- Dashboard running on localhost:3001
- All P1 and P2 features verified working

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-21 15:58:00 -05:00
parent ca25fbdec5
commit f76bd52894
8 changed files with 2740 additions and 0 deletions

View File

@@ -0,0 +1,270 @@
/**
* Parallel Coordinates Plot - Protocol 13
* High-dimensional visualization for multi-objective Pareto fronts
* Shows objectives and design variables as parallel axes
*/
import { useState } from 'react';
interface ParetoTrial {
trial_number: number;
values: number[];
params: Record<string, number>;
constraint_satisfied?: boolean;
}
interface Objective {
name: string;
type: 'minimize' | 'maximize';
unit?: string;
}
interface DesignVariable {
name: string;
unit?: string;
min: number;
max: number;
}
interface ParallelCoordinatesPlotProps {
paretoData: ParetoTrial[];
objectives: Objective[];
designVariables: DesignVariable[];
}
export function ParallelCoordinatesPlot({
paretoData,
objectives,
designVariables
}: ParallelCoordinatesPlotProps) {
const [hoveredTrial, setHoveredTrial] = useState<number | null>(null);
const [selectedTrials, setSelectedTrials] = useState<Set<number>>(new Set());
if (paretoData.length === 0) {
return (
<div className="bg-dark-700 rounded-lg p-6 border border-dark-600">
<h3 className="text-lg font-semibold mb-4 text-dark-100">Parallel Coordinates</h3>
<div className="h-96 flex items-center justify-center text-dark-300">
No Pareto front data yet
</div>
</div>
);
}
// Combine objectives and design variables into axes
const axes: Array<{name: string, label: string, type: 'objective' | 'param'}> = [
...objectives.map((obj, i) => ({
name: `obj_${i}`,
label: obj.unit ? `${obj.name} (${obj.unit})` : obj.name,
type: 'objective' as const
})),
...designVariables.map(dv => ({
name: dv.name,
label: dv.unit ? `${dv.name} (${dv.unit})` : dv.name,
type: 'param' as const
}))
];
// Normalize data to [0, 1] for each axis
const normalizedData = paretoData.map(trial => {
const allValues: number[] = [];
// Add objectives
trial.values.forEach(val => allValues.push(val));
// Add design variables
designVariables.forEach(dv => {
allValues.push(trial.params[dv.name]);
});
return {
trial_number: trial.trial_number,
values: allValues,
feasible: trial.constraint_satisfied !== false
};
});
// Calculate min/max for each axis
const ranges = axes.map((_, axisIdx) => {
const values = normalizedData.map(d => d.values[axisIdx]);
return {
min: Math.min(...values),
max: Math.max(...values)
};
});
// Normalize function
const normalize = (value: number, axisIdx: number): number => {
const range = ranges[axisIdx];
if (range.max === range.min) return 0.5;
return (value - range.min) / (range.max - range.min);
};
// Chart dimensions
const width = 800;
const height = 400;
const margin = { top: 80, right: 20, bottom: 40, left: 20 };
const plotWidth = width - margin.left - margin.right;
const plotHeight = height - margin.top - margin.bottom;
const axisSpacing = plotWidth / (axes.length - 1);
// Toggle trial selection
const toggleTrial = (trialNum: number) => {
const newSelected = new Set(selectedTrials);
if (newSelected.has(trialNum)) {
newSelected.delete(trialNum);
} else {
newSelected.add(trialNum);
}
setSelectedTrials(newSelected);
};
return (
<div className="bg-dark-700 rounded-lg p-6 border border-dark-600">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-dark-100">
Parallel Coordinates ({paretoData.length} solutions)
</h3>
{selectedTrials.size > 0 && (
<button
onClick={() => setSelectedTrials(new Set())}
className="text-xs px-3 py-1 bg-dark-600 hover:bg-dark-500 rounded text-dark-200"
>
Clear Selection ({selectedTrials.size})
</button>
)}
</div>
<svg width={width} height={height} className="overflow-visible">
<g transform={`translate(${margin.left}, ${margin.top})`}>
{/* Draw axes */}
{axes.map((axis, i) => {
const x = i * axisSpacing;
return (
<g key={axis.name} transform={`translate(${x}, 0)`}>
{/* Axis line */}
<line
y1={0}
y2={plotHeight}
stroke="#475569"
strokeWidth={2}
/>
{/* Axis label */}
<text
y={-10}
textAnchor="middle"
fill="#94a3b8"
fontSize={12}
className="select-none"
transform={`rotate(-45, 0, -10)`}
>
{axis.label}
</text>
{/* Min/max labels */}
<text
y={plotHeight + 15}
textAnchor="middle"
fill="#64748b"
fontSize={10}
>
{ranges[i].min.toFixed(2)}
</text>
<text
y={-25}
textAnchor="middle"
fill="#64748b"
fontSize={10}
>
{ranges[i].max.toFixed(2)}
</text>
</g>
);
})}
{/* Draw lines for each trial */}
{normalizedData.map(trial => {
const isHovered = hoveredTrial === trial.trial_number;
const isSelected = selectedTrials.has(trial.trial_number);
const isHighlighted = isHovered || isSelected;
// Build path
const pathData = axes.map((_, i) => {
const x = i * axisSpacing;
const normalizedY = normalize(trial.values[i], i);
const y = plotHeight * (1 - normalizedY);
return i === 0 ? `M ${x} ${y}` : `L ${x} ${y}`;
}).join(' ');
return (
<path
key={trial.trial_number}
d={pathData}
fill="none"
stroke={
isSelected ? '#fbbf24' :
trial.feasible ? '#10b981' : '#ef4444'
}
strokeWidth={isHighlighted ? 2.5 : 1}
opacity={
selectedTrials.size > 0
? (isSelected ? 1 : 0.1)
: (isHighlighted ? 1 : 0.4)
}
strokeLinecap="round"
strokeLinejoin="round"
className="transition-all duration-200 cursor-pointer"
onMouseEnter={() => setHoveredTrial(trial.trial_number)}
onMouseLeave={() => setHoveredTrial(null)}
onClick={() => toggleTrial(trial.trial_number)}
/>
);
})}
{/* Hover tooltip */}
{hoveredTrial !== null && (
<g transform={`translate(${plotWidth + 10}, 20)`}>
<rect
x={0}
y={0}
width={120}
height={60}
fill="#1e293b"
stroke="#334155"
strokeWidth={1}
rx={4}
/>
<text x={10} y={20} fill="#e2e8f0" fontSize={12} fontWeight="bold">
Trial #{hoveredTrial}
</text>
<text x={10} y={38} fill="#94a3b8" fontSize={10}>
Click to select
</text>
<text x={10} y={52} fill="#94a3b8" fontSize={10}>
{selectedTrials.has(hoveredTrial) ? '✓ Selected' : '○ Not selected'}
</text>
</g>
)}
</g>
</svg>
{/* Legend */}
<div className="flex gap-6 justify-center mt-4 text-sm">
<div className="flex items-center gap-2">
<div className="w-8 h-0.5 bg-green-400" />
<span className="text-dark-200">Feasible</span>
</div>
<div className="flex items-center gap-2">
<div className="w-8 h-0.5 bg-red-400" />
<span className="text-dark-200">Infeasible</span>
</div>
<div className="flex items-center gap-2">
<div className="w-8 h-0.5 bg-yellow-400" style={{ height: '2px' }} />
<span className="text-dark-200">Selected</span>
</div>
</div>
</div>
);
}