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:
2025-11-25 19:23:58 -05:00
parent 74a92803b7
commit e3bdb08a22
155 changed files with 52729 additions and 37 deletions

View File

@@ -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' }} />