feat: Add Analysis page, run comparison, notifications, and config editor
Dashboard enhancements:
- Add Analysis page with tabs: Overview, Parameters, Pareto, Correlations, Constraints, Surrogate, Runs
- Add PlotlyCorrelationHeatmap for parameter-objective correlation analysis
- Add PlotlyFeasibilityChart for constraint satisfaction visualization
- Add PlotlySurrogateQuality for FEA vs NN prediction comparison
- Add PlotlyRunComparison for comparing optimization runs within a study
Real-time improvements:
- Replace watchdog file-watching with SQLite database polling for better Windows reliability
- Add DatabasePoller class with 2-second polling interval
- Enhanced WebSocket messages: trial_completed, new_best, pareto_update, progress
Desktop notifications:
- Add useNotifications hook using Web Notifications API
- Add NotificationSettings toggle component
- Notify users when new best solutions are found
Config editor:
- Add PUT /studies/{study_id}/config endpoint with auto-backup
- Add ConfigEditor modal with tabs: General, Variables, Objectives, Settings, JSON
- Prevents editing while optimization is running
Enhanced Pareto visualization:
- Add dark mode styling with transparent backgrounds
- Add stats bar showing Pareto, FEA, NN, and infeasible counts
- Add Pareto front connecting line for 2D view
- Add table showing top 10 Pareto-optimal solutions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
import { useMemo } from 'react';
|
||||
import Plot from 'react-plotly.js';
|
||||
|
||||
interface TrialData {
|
||||
trial_number: number;
|
||||
values: number[];
|
||||
source?: 'FEA' | 'NN' | 'V10_FEA';
|
||||
user_attrs?: Record<string, any>;
|
||||
}
|
||||
|
||||
interface PlotlySurrogateQualityProps {
|
||||
trials: TrialData[];
|
||||
height?: number;
|
||||
}
|
||||
|
||||
export function PlotlySurrogateQuality({
|
||||
trials,
|
||||
height = 400
|
||||
}: PlotlySurrogateQualityProps) {
|
||||
const { feaTrials, nnTrials, timeline } = useMemo(() => {
|
||||
const fea = trials.filter(t => t.source === 'FEA' || t.source === 'V10_FEA');
|
||||
const nn = trials.filter(t => t.source === 'NN');
|
||||
|
||||
// Sort by trial number for timeline
|
||||
const sorted = [...trials].sort((a, b) => a.trial_number - b.trial_number);
|
||||
|
||||
// Calculate source distribution over time
|
||||
const timeline: { trial: number; feaCount: number; nnCount: number }[] = [];
|
||||
let feaCount = 0;
|
||||
let nnCount = 0;
|
||||
|
||||
sorted.forEach(t => {
|
||||
if (t.source === 'NN') nnCount++;
|
||||
else feaCount++;
|
||||
|
||||
timeline.push({
|
||||
trial: t.trial_number,
|
||||
feaCount,
|
||||
nnCount
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
feaTrials: fea,
|
||||
nnTrials: nn,
|
||||
timeline
|
||||
};
|
||||
}, [trials]);
|
||||
|
||||
if (nnTrials.length === 0) {
|
||||
return (
|
||||
<div className="h-64 flex items-center justify-center text-dark-400">
|
||||
<p>No neural network evaluations in this study</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Objective distribution by source
|
||||
const feaObjectives = feaTrials.map(t => t.values[0]).filter(v => v !== undefined && !isNaN(v));
|
||||
const nnObjectives = nnTrials.map(t => t.values[0]).filter(v => v !== undefined && !isNaN(v));
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Source Distribution Over Time */}
|
||||
<Plot
|
||||
data={[
|
||||
{
|
||||
x: timeline.map(t => t.trial),
|
||||
y: timeline.map(t => t.feaCount),
|
||||
type: 'scatter',
|
||||
mode: 'lines',
|
||||
name: 'FEA Cumulative',
|
||||
line: { color: '#3b82f6', width: 2 },
|
||||
fill: 'tozeroy',
|
||||
fillcolor: 'rgba(59, 130, 246, 0.2)'
|
||||
},
|
||||
{
|
||||
x: timeline.map(t => t.trial),
|
||||
y: timeline.map(t => t.nnCount),
|
||||
type: 'scatter',
|
||||
mode: 'lines',
|
||||
name: 'NN Cumulative',
|
||||
line: { color: '#a855f7', width: 2 },
|
||||
fill: 'tozeroy',
|
||||
fillcolor: 'rgba(168, 85, 247, 0.2)'
|
||||
}
|
||||
]}
|
||||
layout={{
|
||||
title: {
|
||||
text: 'Evaluation Source Over Time',
|
||||
font: { color: '#fff', size: 14 }
|
||||
},
|
||||
height: height * 0.6,
|
||||
margin: { l: 60, r: 30, t: 50, b: 50 },
|
||||
paper_bgcolor: 'transparent',
|
||||
plot_bgcolor: 'transparent',
|
||||
xaxis: {
|
||||
title: { text: 'Trial Number', font: { color: '#888' } },
|
||||
tickfont: { color: '#888' },
|
||||
gridcolor: 'rgba(255,255,255,0.05)'
|
||||
},
|
||||
yaxis: {
|
||||
title: { text: 'Cumulative Count', font: { color: '#888' } },
|
||||
tickfont: { color: '#888' },
|
||||
gridcolor: 'rgba(255,255,255,0.1)'
|
||||
},
|
||||
legend: {
|
||||
font: { color: '#888' },
|
||||
bgcolor: 'rgba(0,0,0,0.5)',
|
||||
orientation: 'h',
|
||||
y: 1.1
|
||||
},
|
||||
showlegend: true
|
||||
}}
|
||||
config={{
|
||||
displayModeBar: true,
|
||||
modeBarButtonsToRemove: ['lasso2d', 'select2d'],
|
||||
displaylogo: false
|
||||
}}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
|
||||
{/* Objective Distribution by Source */}
|
||||
<Plot
|
||||
data={[
|
||||
{
|
||||
x: feaObjectives,
|
||||
type: 'histogram',
|
||||
name: 'FEA',
|
||||
marker: { color: 'rgba(59, 130, 246, 0.7)' },
|
||||
opacity: 0.8
|
||||
} as any,
|
||||
{
|
||||
x: nnObjectives,
|
||||
type: 'histogram',
|
||||
name: 'NN',
|
||||
marker: { color: 'rgba(168, 85, 247, 0.7)' },
|
||||
opacity: 0.8
|
||||
} as any
|
||||
]}
|
||||
layout={{
|
||||
title: {
|
||||
text: 'Objective Distribution by Source',
|
||||
font: { color: '#fff', size: 14 }
|
||||
},
|
||||
height: height * 0.5,
|
||||
margin: { l: 60, r: 30, t: 50, b: 50 },
|
||||
paper_bgcolor: 'transparent',
|
||||
plot_bgcolor: 'transparent',
|
||||
xaxis: {
|
||||
title: { text: 'Objective Value', font: { color: '#888' } },
|
||||
tickfont: { color: '#888' },
|
||||
gridcolor: 'rgba(255,255,255,0.05)'
|
||||
},
|
||||
yaxis: {
|
||||
title: { text: 'Count', font: { color: '#888' } },
|
||||
tickfont: { color: '#888' },
|
||||
gridcolor: 'rgba(255,255,255,0.1)'
|
||||
},
|
||||
barmode: 'overlay',
|
||||
legend: {
|
||||
font: { color: '#888' },
|
||||
bgcolor: 'rgba(0,0,0,0.5)',
|
||||
orientation: 'h',
|
||||
y: 1.1
|
||||
},
|
||||
showlegend: true
|
||||
}}
|
||||
config={{
|
||||
displayModeBar: true,
|
||||
modeBarButtonsToRemove: ['lasso2d', 'select2d'],
|
||||
displaylogo: false
|
||||
}}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
|
||||
{/* FEA vs NN Best Values Comparison */}
|
||||
{feaObjectives.length > 0 && nnObjectives.length > 0 && (
|
||||
<div className="grid grid-cols-2 gap-4 mt-4">
|
||||
<div className="bg-dark-750 rounded-lg p-4 border border-dark-600">
|
||||
<div className="text-xs text-dark-400 uppercase mb-2">FEA Best</div>
|
||||
<div className="text-xl font-mono text-blue-400">
|
||||
{Math.min(...feaObjectives).toExponential(4)}
|
||||
</div>
|
||||
<div className="text-xs text-dark-500 mt-1">
|
||||
from {feaObjectives.length} evaluations
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-dark-750 rounded-lg p-4 border border-dark-600">
|
||||
<div className="text-xs text-dark-400 uppercase mb-2">NN Best</div>
|
||||
<div className="text-xl font-mono text-purple-400">
|
||||
{Math.min(...nnObjectives).toExponential(4)}
|
||||
</div>
|
||||
<div className="text-xs text-dark-500 mt-1">
|
||||
from {nnObjectives.length} predictions
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user