feat: Major update with validators, skills, dashboard, and docs reorganization
- Add validation framework (config, model, results, study validators) - Add Claude Code skills (create-study, run-optimization, generate-report, troubleshoot, analyze-model) - Add Atomizer Dashboard (React frontend + FastAPI backend) - Reorganize docs into structured directories (00-09) - Add neural surrogate modules and training infrastructure - Add multi-objective optimization support 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -39,17 +39,22 @@ interface ParallelCoordinatesPlotProps {
|
||||
objectives: Objective[];
|
||||
designVariables: DesignVariable[];
|
||||
constraints?: Constraint[];
|
||||
paretoFront?: ParetoTrial[];
|
||||
}
|
||||
|
||||
export function ParallelCoordinatesPlot({
|
||||
paretoData,
|
||||
objectives,
|
||||
designVariables,
|
||||
constraints = []
|
||||
constraints = [],
|
||||
paretoFront = []
|
||||
}: ParallelCoordinatesPlotProps) {
|
||||
const [hoveredTrial, setHoveredTrial] = useState<number | null>(null);
|
||||
const [selectedTrials, setSelectedTrials] = useState<Set<number>>(new Set());
|
||||
|
||||
// Create set of Pareto front trial numbers for easy lookup
|
||||
const paretoTrialNumbers = new Set(paretoFront.map(t => t.trial_number));
|
||||
|
||||
// Safety checks
|
||||
if (!paretoData || paretoData.length === 0) {
|
||||
return (
|
||||
@@ -83,9 +88,10 @@ export function ParallelCoordinatesPlot({
|
||||
|
||||
// Add design variables
|
||||
designVariables.forEach(dv => {
|
||||
const paramName = dv.parameter || dv.name; // Support both formats
|
||||
axes.push({
|
||||
name: dv.name,
|
||||
label: dv.unit ? `${dv.name}\n(${dv.unit})` : dv.name,
|
||||
name: paramName,
|
||||
label: dv.unit ? `${paramName}\n(${dv.unit})` : paramName,
|
||||
type: 'design_var',
|
||||
unit: dv.unit
|
||||
});
|
||||
@@ -134,9 +140,10 @@ export function ParallelCoordinatesPlot({
|
||||
const trialData = paretoData.map(trial => {
|
||||
const values: number[] = [];
|
||||
|
||||
// Design variables
|
||||
// Design variables - use .parameter field from metadata
|
||||
designVariables.forEach(dv => {
|
||||
values.push(trial.params[dv.name] ?? 0);
|
||||
const paramName = dv.parameter || dv.name; // Support both formats
|
||||
values.push(trial.params[paramName] ?? 0);
|
||||
});
|
||||
|
||||
// Objectives
|
||||
@@ -152,10 +159,32 @@ export function ParallelCoordinatesPlot({
|
||||
return {
|
||||
trial_number: trial.trial_number,
|
||||
values,
|
||||
feasible: trial.constraint_satisfied !== false
|
||||
feasible: trial.constraint_satisfied !== false,
|
||||
objectiveValues: trial.values || []
|
||||
};
|
||||
});
|
||||
|
||||
// Rank trials by their first objective (for multi-objective, this is just one metric)
|
||||
// For proper multi-objective ranking, we use Pareto dominance
|
||||
const rankedTrials = [...trialData].sort((a, b) => {
|
||||
// Primary: Pareto front members come first
|
||||
const aIsPareto = paretoTrialNumbers.has(a.trial_number);
|
||||
const bIsPareto = paretoTrialNumbers.has(b.trial_number);
|
||||
if (aIsPareto && !bIsPareto) return -1;
|
||||
if (!aIsPareto && bIsPareto) return 1;
|
||||
|
||||
// Secondary: Sort by first objective value (minimize assumed)
|
||||
const aObj = a.objectiveValues[0] ?? Infinity;
|
||||
const bObj = b.objectiveValues[0] ?? Infinity;
|
||||
return aObj - bObj;
|
||||
});
|
||||
|
||||
// Create ranking map: trial_number -> rank (0-indexed)
|
||||
const trialRanks = new Map<number, number>();
|
||||
rankedTrials.forEach((trial, index) => {
|
||||
trialRanks.set(trial.trial_number, index);
|
||||
});
|
||||
|
||||
// Calculate min/max for normalization
|
||||
const ranges = axes.map((_, axisIdx) => {
|
||||
const values = trialData.map(d => d.values[axisIdx]);
|
||||
@@ -192,12 +221,26 @@ export function ParallelCoordinatesPlot({
|
||||
setSelectedTrials(newSelected);
|
||||
};
|
||||
|
||||
// Color scheme - highly visible
|
||||
// Color scheme - gradient grayscale for top 10, light gray for rest
|
||||
const getLineColor = (trial: typeof trialData[0], isHovered: boolean, isSelected: boolean) => {
|
||||
if (isSelected) return '#FF6B00'; // Bright orange for selected
|
||||
if (!trial.feasible) return '#DC2626'; // Red for infeasible
|
||||
if (isHovered) return '#2563EB'; // Blue for hover
|
||||
return '#10B981'; // Green for feasible
|
||||
if (!trial.feasible) return '#DC2626'; // Red for infeasible
|
||||
|
||||
const rank = trialRanks.get(trial.trial_number) ?? 999;
|
||||
|
||||
// Top 10: Gradient from dark gray (#374151) to light gray (#9CA3AF)
|
||||
if (rank < 10) {
|
||||
// Interpolate: rank 0 = darkest, rank 9 = lighter
|
||||
const t = rank / 9; // 0 to 1
|
||||
const r = Math.round(55 + t * (156 - 55)); // 55 to 156
|
||||
const g = Math.round(65 + t * (163 - 65)); // 65 to 163
|
||||
const b = Math.round(81 + t * (175 - 81)); // 81 to 175
|
||||
return `rgb(${r}, ${g}, ${b})`;
|
||||
}
|
||||
|
||||
// Remaining trials: Very light gray
|
||||
return '#D1D5DB'; // Very light gray
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -371,8 +414,12 @@ export function ParallelCoordinatesPlot({
|
||||
{/* Legend */}
|
||||
<div className="flex gap-8 justify-center mt-6 text-sm border-t border-gray-200 pt-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-10 h-1" style={{ backgroundColor: '#10B981' }} />
|
||||
<span className="text-gray-700 font-medium">Feasible</span>
|
||||
<div className="w-10 h-1" style={{ background: 'linear-gradient(to right, #374151, #9CA3AF)' }} />
|
||||
<span className="text-gray-700 font-medium">Top 10 (gradient)</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-10 h-1" style={{ backgroundColor: '#D1D5DB' }} />
|
||||
<span className="text-gray-700 font-medium">Others</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-10 h-1" style={{ backgroundColor: '#DC2626' }} />
|
||||
|
||||
Reference in New Issue
Block a user