/** * Pareto Front Plot - Protocol 13 * Visualizes Pareto-optimal solutions for multi-objective optimization */ import { useState } from 'react'; import { ScatterChart, Scatter, Line, XAxis, YAxis, CartesianGrid, Tooltip, Cell, ResponsiveContainer, Legend } from 'recharts'; interface ParetoTrial { trial_number: number; values: number[]; // Support variable number of objectives params: Record; constraint_satisfied?: boolean; } interface Objective { name: string; type?: 'minimize' | 'maximize'; direction?: 'minimize' | 'maximize'; // Alternative field used by some configs unit?: string; } interface ParetoPlotProps { paretoData: ParetoTrial[]; objectives: Objective[]; allTrials?: ParetoTrial[]; // All trials including non-Pareto } type NormalizationMode = 'raw' | 'minmax' | 'zscore'; export function ParetoPlot({ paretoData, objectives, allTrials }: ParetoPlotProps) { const [normMode, setNormMode] = useState('raw'); // Use allTrials if provided, otherwise fall back to paretoData const trialsToShow = allTrials && allTrials.length > 0 ? allTrials : paretoData; if (trialsToShow.length === 0) { return (

Pareto Front

No Pareto front data yet
); } // Create set of Pareto front trial numbers for easy lookup const paretoTrialNumbers = new Set(paretoData.map(t => t.trial_number)); // Extract raw values for ALL trials const rawData = trialsToShow.map(trial => ({ x: trial.values[0], y: trial.values[1], trial_number: trial.trial_number, feasible: trial.constraint_satisfied !== false, isPareto: paretoTrialNumbers.has(trial.trial_number) })); // Calculate statistics for normalization const xValues = rawData.map(d => d.x); const yValues = rawData.map(d => d.y); const xMin = Math.min(...xValues); const xMax = Math.max(...xValues); const yMin = Math.min(...yValues); const yMax = Math.max(...yValues); const xMean = xValues.reduce((a, b) => a + b, 0) / xValues.length; const yMean = yValues.reduce((a, b) => a + b, 0) / yValues.length; const xStd = Math.sqrt(xValues.reduce((sum, val) => sum + Math.pow(val - xMean, 2), 0) / xValues.length); const yStd = Math.sqrt(yValues.reduce((sum, val) => sum + Math.pow(val - yMean, 2), 0) / yValues.length); // Normalize data based on selected mode const normalizeX = (val: number): number => { if (normMode === 'minmax') { return xMax === xMin ? 0.5 : (val - xMin) / (xMax - xMin); } else if (normMode === 'zscore') { return xStd === 0 ? 0 : (val - xMean) / xStd; } return val; // raw }; const normalizeY = (val: number): number => { if (normMode === 'minmax') { return yMax === yMin ? 0.5 : (val - yMin) / (yMax - yMin); } else if (normMode === 'zscore') { return yStd === 0 ? 0 : (val - yMean) / yStd; } return val; // raw }; // Transform data with normalization const data = rawData.map(d => ({ x: normalizeX(d.x), y: normalizeY(d.y), rawX: d.x, rawY: d.y, trial_number: d.trial_number, feasible: d.feasible, isPareto: d.isPareto })); // Sort ONLY Pareto front data by x-coordinate for line const paretoOnlyData = data.filter(d => d.isPareto).sort((a, b) => a.x - b.x); // Get objective labels with normalization indicator const normSuffix = normMode === 'minmax' ? ' [0-1]' : normMode === 'zscore' ? ' [z-score]' : ''; const xLabel = objectives[0] ? `${objectives[0].name}${objectives[0].unit ? ` (${objectives[0].unit})` : ''}${normSuffix}` : `Objective 1${normSuffix}`; const yLabel = objectives[1] ? `${objectives[1].name}${objectives[1].unit ? ` (${objectives[1].unit})` : ''}${normSuffix}` : `Objective 2${normSuffix}`; // Custom tooltip (always shows raw values) const CustomTooltip = ({ active, payload }: any) => { if (!active || !payload || payload.length === 0) return null; const point = payload[0].payload; return (
Trial #{point.trial_number}
{objectives[0]?.name || 'Obj 1'}: {point.rawX.toFixed(4)}
{objectives[1]?.name || 'Obj 2'}: {point.rawY.toFixed(4)}
{point.feasible ? '✓ Feasible' : '✗ Infeasible'}
); }; return (

Pareto Front ({paretoData.length} solutions)

{/* Normalization Toggle */}
} /> (
Feasible
Infeasible
)} /> {/* Pareto front line - only connects Pareto front points */} {/* All trials as scatter points */} {data.map((entry, index) => ( ))}
); }