Files
Atomizer/atomizer.py
Anto01 a26914bbe8 feat: Add Studio UI, intake system, and extractor improvements
Dashboard:
- Add Studio page with drag-drop model upload and Claude chat
- Add intake system for study creation workflow
- Improve session manager and context builder
- Add intake API routes and frontend components

Optimization Engine:
- Add CLI module for command-line operations
- Add intake module for study preprocessing
- Add validation module with gate checks
- Improve Zernike extractor documentation
- Update spec models with better validation
- Enhance solve_simulation robustness

Documentation:
- Add ATOMIZER_STUDIO.md planning doc
- Add ATOMIZER_UX_SYSTEM.md for UX patterns
- Update extractor library docs
- Add study-readme-generator skill

Tools:
- Add test scripts for extraction validation
- Add Zernike recentering test

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 12:02:30 -05:00

741 lines
24 KiB
Python

#!/usr/bin/env python
"""
Atomizer CLI - Neural-Accelerated Structural Optimization
One-command interface for the complete Atomizer workflow:
- Create studies from templates
- Run FEA optimizations with auto training data export
- Auto-train neural networks when data threshold is reached
- Run neural-accelerated optimization (2200x faster!)
Usage:
python atomizer.py neural-optimize --study my_study --trials 500
python atomizer.py create-study --template beam_stiffness --name my_beam
python atomizer.py status --study my_study
python atomizer.py train --study my_study --epochs 100
The neural-optimize command is the main entry point - it handles the complete
workflow automatically:
1. Runs FEA optimization with training data export
2. Triggers neural network training when enough data is collected
3. Switches to neural-accelerated mode for remaining trials
4. Detects model drift and retrains as needed
"""
import argparse
import json
import logging
import sys
import time
from pathlib import Path
from typing import Optional
# Add project root to path
PROJECT_ROOT = Path(__file__).parent
sys.path.insert(0, str(PROJECT_ROOT))
from optimization_engine.processors.surrogates.auto_trainer import (
AutoTrainer,
check_training_status,
)
from optimization_engine.config.template_loader import (
create_study_from_template,
list_templates,
get_template,
)
from optimization_engine.validators.study_validator import validate_study, list_studies, quick_check
# New UX System imports (lazy loaded to avoid import errors)
def get_intake_processor():
from optimization_engine.intake import IntakeProcessor
return IntakeProcessor
def get_validation_gate():
from optimization_engine.validation import ValidationGate
return ValidationGate
def get_report_generator():
from optimization_engine.reporting.html_report import HTMLReportGenerator
return HTMLReportGenerator
def setup_logging(verbose: bool = False) -> None:
"""Configure logging."""
level = logging.DEBUG if verbose else logging.INFO
logging.basicConfig(
level=level, format="%(asctime)s [%(levelname)s] %(message)s", datefmt="%H:%M:%S"
)
def cmd_neural_optimize(args) -> int:
"""
Run neural-accelerated optimization.
This is the main workflow that:
1. Validates study setup
2. Runs FEA exploration with training data export
3. Auto-trains neural model when threshold reached
4. Runs remaining trials with neural acceleration
"""
print("=" * 60)
print("ATOMIZER NEURAL-ACCELERATED OPTIMIZATION")
print("=" * 60)
print(f"Study: {args.study}")
print(f"Total trials: {args.trials}")
print(f"Auto-train threshold: {args.min_points} points")
print(f"Retrain every: {args.retrain_every} new points")
print("=" * 60)
# Validate study
print("\n[1/5] Validating study setup...")
validation = validate_study(args.study)
if not validation.is_ready_to_run:
print(f"\nStudy validation failed:")
print(validation)
return 1
print(f" Study is ready to run")
print(f" Design variables: {validation.summary.get('design_variables', 0)}")
print(f" Objectives: {validation.summary.get('objectives', 0)}")
# Initialize auto-trainer
print("\n[2/5] Initializing auto-trainer...")
trainer = AutoTrainer(
study_name=args.study,
min_points=args.min_points,
epochs=args.epochs,
retrain_threshold=args.retrain_every,
)
status = trainer.get_status()
print(f" Current data points: {status['total_points']}")
print(f" Model version: v{status['model_version']}")
# Determine workflow phase
has_trained_model = status["model_version"] > 0
current_points = status["total_points"]
if has_trained_model and current_points >= args.min_points:
print("\n[3/5] Neural model available - starting neural-accelerated optimization...")
return _run_neural_phase(args, trainer)
else:
print("\n[3/5] Building training dataset with FEA exploration...")
return _run_exploration_phase(args, trainer)
def _run_exploration_phase(args, trainer: AutoTrainer) -> int:
"""Run FEA exploration to build training dataset."""
study_dir = PROJECT_ROOT / "studies" / args.study
run_script = study_dir / "run_optimization.py"
if not run_script.exists():
print(f"Error: run_optimization.py not found in {study_dir}")
return 1
# Calculate how many FEA trials we need
current_points = trainer.count_training_points()
needed_for_training = args.min_points - current_points
if needed_for_training > 0:
fea_trials = min(needed_for_training + 10, args.trials) # Extra buffer
print(f"\n Running {fea_trials} FEA trials to build training data...")
print(f" (Need {needed_for_training} more points for neural training)")
else:
fea_trials = args.trials
print(f"\n Running {fea_trials} FEA trials...")
# Run FEA optimization
import subprocess
cmd = [sys.executable, str(run_script), "--trials", str(fea_trials)]
if args.resume:
cmd.append("--resume")
print(f"\n[4/5] Executing: {' '.join(cmd)}")
print("-" * 60)
start_time = time.time()
result = subprocess.run(cmd, cwd=str(study_dir))
elapsed = time.time() - start_time
print("-" * 60)
print(f"FEA optimization completed in {elapsed / 60:.1f} minutes")
# Check if we can now train
print("\n[5/5] Checking training data...")
if trainer.should_train():
print(" Threshold reached! Training neural model...")
model_path = trainer.train()
if model_path:
print(f" Neural model trained: {model_path}")
print(f"\n Re-run with --resume to continue with neural acceleration!")
else:
print(" Training failed - check logs")
else:
status = trainer.get_status()
remaining = args.min_points - status["total_points"]
print(f" {status['total_points']} points collected")
print(f" Need {remaining} more for neural training")
return result.returncode
def _run_neural_phase(args, trainer: AutoTrainer) -> int:
"""Run neural-accelerated optimization."""
study_dir = PROJECT_ROOT / "studies" / args.study
run_script = study_dir / "run_optimization.py"
if not run_script.exists():
print(f"Error: run_optimization.py not found in {study_dir}")
return 1
# Run with neural acceleration
import subprocess
cmd = [sys.executable, str(run_script), "--trials", str(args.trials), "--enable-nn"]
if args.resume:
cmd.append("--resume")
print(f"\n[4/5] Executing: {' '.join(cmd)}")
print("-" * 60)
start_time = time.time()
result = subprocess.run(cmd, cwd=str(study_dir))
elapsed = time.time() - start_time
print("-" * 60)
print(f"Neural optimization completed in {elapsed / 60:.1f} minutes")
# Check for retraining
print("\n[5/5] Checking if retraining needed...")
if trainer.should_train():
print(" New data accumulated - triggering retraining...")
model_path = trainer.train()
if model_path:
print(f" New model version: {model_path}")
else:
status = trainer.get_status()
print(f" {status['new_points_since_training']} new points since last training")
print(f" (Retrain threshold: {args.retrain_every})")
return result.returncode
def cmd_create_study(args) -> int:
"""Create a new study from template."""
print(f"Creating study '{args.name}' from template '{args.template}'...")
try:
study_path = create_study_from_template(template_name=args.template, study_name=args.name)
print(f"\nSuccess! Study created at: {study_path}")
return 0
except FileNotFoundError as e:
print(f"Error: {e}")
return 1
except FileExistsError as e:
print(f"Error: {e}")
return 1
def cmd_list_templates(args) -> int:
"""List available templates."""
templates = list_templates()
if not templates:
print("No templates found in templates/")
return 1
print("\nAvailable Templates:")
print("=" * 60)
for t in templates:
print(f"\n{t['name']}")
print(f" {t['description']}")
print(f" Category: {t['category']} | Analysis: {t['analysis_type']}")
print(f" Design vars: {t['design_variables']} | Objectives: {t['objectives']}")
print("\n" + "=" * 60)
print("Use: atomizer create-study --template <name> --name <study_name>")
return 0
def cmd_status(args) -> int:
"""Show study and training status."""
if args.study:
# Show specific study status
print(f"\n=== Study: {args.study} ===\n")
# Validation status
validation = validate_study(args.study)
print("VALIDATION STATUS")
print("-" * 40)
print(f" Status: {validation.status.value}")
print(f" Ready to run: {validation.is_ready_to_run}")
for key, value in validation.summary.items():
print(f" {key}: {value}")
# Training status
print("\nTRAINING DATA STATUS")
print("-" * 40)
status = check_training_status(args.study)
print(f" Data points: {status['total_points']}")
print(f" New since training: {status['new_points_since_training']}")
print(f" Model version: v{status['model_version']}")
print(f" Should train: {status['should_train']}")
if status["latest_model"]:
print(f" Latest model: {status['latest_model']}")
else:
# List all studies
print("\nAll Studies:")
print("=" * 60)
studies = list_studies()
if not studies:
print(" No studies found in studies/")
return 0
for study in studies:
icon = "[OK]" if study["is_ready"] else "[!]"
trials_info = f"{study['trials']} trials" if study["trials"] > 0 else "no trials"
pareto_info = f", {study['pareto']} Pareto" if study["pareto"] > 0 else ""
print(f" {icon} {study['name']}")
print(f" Status: {study['status']} ({trials_info}{pareto_info})")
return 0
def cmd_train(args) -> int:
"""Trigger neural network training."""
print(f"Training neural model for study: {args.study}")
trainer = AutoTrainer(study_name=args.study, min_points=args.min_points, epochs=args.epochs)
status = trainer.get_status()
print(f"\nCurrent status:")
print(f" Data points: {status['total_points']}")
print(f" Min threshold: {args.min_points}")
if args.force or trainer.should_train():
if args.force and status["total_points"] < args.min_points:
print(
f"\nWarning: Force training with {status['total_points']} points (< {args.min_points})"
)
print("\nStarting training...")
model_path = trainer.train()
if model_path:
print(f"\nSuccess! Model saved to: {model_path}")
return 0
else:
print("\nTraining failed - check logs")
return 1
else:
needed = args.min_points - status["total_points"]
print(f"\nNot enough data for training. Need {needed} more points.")
print("Use --force to train anyway.")
return 1
def cmd_validate(args) -> int:
"""Validate study setup."""
validation = validate_study(args.study)
print(validation)
return 0 if validation.is_ready_to_run else 1
# ============================================================================
# NEW UX SYSTEM COMMANDS
# ============================================================================
def cmd_intake(args) -> int:
"""Process an intake folder into a study."""
IntakeProcessor = get_intake_processor()
# Determine inbox folder
inbox_path = Path(args.folder)
if not inbox_path.is_absolute():
inbox_dir = PROJECT_ROOT / "studies" / "_inbox"
if (inbox_dir / args.folder).exists():
inbox_path = inbox_dir / args.folder
elif (PROJECT_ROOT / "studies" / args.folder).exists():
inbox_path = PROJECT_ROOT / "studies" / args.folder
if not inbox_path.exists():
print(f"Error: Folder not found: {inbox_path}")
return 1
print(f"Processing intake: {inbox_path}")
print("=" * 60)
def progress(message: str, percent: float):
bar_width = 30
filled = int(bar_width * percent)
bar = "=" * filled + "-" * (bar_width - filled)
print(f"\r[{bar}] {percent * 100:5.1f}% {message}", end="", flush=True)
if percent >= 1.0:
print()
try:
processor = IntakeProcessor(inbox_path, progress_callback=progress)
context = processor.process(
run_baseline=not args.skip_baseline,
copy_files=True,
run_introspection=True,
)
print("\n" + "=" * 60)
print("INTAKE COMPLETE")
print("=" * 60)
summary = context.get_context_summary()
print(f"\nStudy: {context.study_name}")
print(f"Location: {processor.study_dir}")
print(f"\nContext loaded:")
print(f" Model: {'Yes' if summary['has_model'] else 'No'}")
print(f" Introspection: {'Yes' if summary['has_introspection'] else 'No'}")
print(f" Baseline: {'Yes' if summary['has_baseline'] else 'No'}")
print(
f" Expressions: {summary['num_expressions']} ({summary['num_dv_candidates']} candidates)"
)
if context.has_baseline:
print(f"\nBaseline: {context.get_baseline_summary()}")
if summary["warnings"]:
print(f"\nWarnings:")
for w in summary["warnings"]:
print(f" - {w}")
print(f"\nNext: atomizer gate {context.study_name}")
return 0
except Exception as e:
print(f"\nError: {e}")
if args.verbose:
import traceback
traceback.print_exc()
return 1
def cmd_gate(args) -> int:
"""Run validation gate before optimization."""
ValidationGate = get_validation_gate()
study_path = Path(args.study)
if not study_path.is_absolute():
study_path = PROJECT_ROOT / "studies" / args.study
if not study_path.exists():
print(f"Error: Study not found: {study_path}")
return 1
print(f"Validation Gate: {study_path.name}")
print("=" * 60)
def progress(message: str, percent: float):
bar_width = 30
filled = int(bar_width * percent)
bar = "=" * filled + "-" * (bar_width - filled)
print(f"\r[{bar}] {percent * 100:5.1f}% {message}", end="", flush=True)
if percent >= 1.0:
print()
try:
gate = ValidationGate(study_path, progress_callback=progress)
result = gate.validate(
run_test_trials=not args.skip_trials,
n_test_trials=args.trials,
)
print("\n" + "=" * 60)
if result.passed:
print("VALIDATION PASSED")
else:
print("VALIDATION FAILED")
print("=" * 60)
# Show test trials
if result.test_trials:
print(
f"\nTest Trials: {len([t for t in result.test_trials if t.success])}/{len(result.test_trials)} passed"
)
if result.results_vary:
print("Results vary: Yes (mesh updating correctly)")
else:
print("Results vary: NO - MESH MAY NOT BE UPDATING!")
# Results table
print(f"\n{'Trial':<8} {'Status':<8} {'Time':<8}", end="")
if result.test_trials and result.test_trials[0].objectives:
for obj in list(result.test_trials[0].objectives.keys())[:3]:
print(f" {obj[:10]:<12}", end="")
print()
for trial in result.test_trials:
status = "OK" if trial.success else "FAIL"
print(
f"{trial.trial_number:<8} {status:<8} {trial.solve_time_seconds:<8.1f}", end=""
)
for val in list(trial.objectives.values())[:3]:
print(f" {val:<12.4f}", end="")
print()
# Runtime estimate
if result.avg_solve_time:
print(f"\nRuntime Estimate:")
print(f" Avg solve: {result.avg_solve_time:.1f}s")
if result.estimated_total_runtime:
print(f" Total: {result.estimated_total_runtime / 3600:.1f}h")
# Errors
if result.errors:
print(f"\nErrors:")
for err in result.errors:
print(f" - {err}")
if result.passed and args.approve:
gate.approve()
print(f"\nStudy approved for optimization!")
elif result.passed:
print(f"\nTo approve: atomizer gate {args.study} --approve")
gate.save_result(result)
return 0 if result.passed else 1
except Exception as e:
print(f"\nError: {e}")
if args.verbose:
import traceback
traceback.print_exc()
return 1
def cmd_finalize(args) -> int:
"""Generate final report for a study."""
HTMLReportGenerator = get_report_generator()
study_path = Path(args.study)
if not study_path.is_absolute():
study_path = PROJECT_ROOT / "studies" / args.study
if not study_path.exists():
print(f"Error: Study not found: {study_path}")
return 1
print(f"Generating report for: {study_path.name}")
print("=" * 60)
try:
generator = HTMLReportGenerator(study_path)
report_path = generator.generate(include_pdf=getattr(args, "pdf", False))
print(f"\nReport generated successfully!")
print(f" HTML: {report_path}")
print(f" Data: {report_path.parent / 'data'}")
if getattr(args, "open", False):
import webbrowser
webbrowser.open(str(report_path))
else:
print(f"\nOpen in browser: file://{report_path}")
return 0
except Exception as e:
print(f"\nError: {e}")
if args.verbose:
import traceback
traceback.print_exc()
return 1
def cmd_list_studies(args) -> int:
"""List all studies and inbox items."""
studies_dir = PROJECT_ROOT / "studies"
print("Atomizer Studies")
print("=" * 60)
# Inbox items
inbox_dir = studies_dir / "_inbox"
if inbox_dir.exists():
inbox_items = [d for d in inbox_dir.iterdir() if d.is_dir() and not d.name.startswith(".")]
if inbox_items:
print("\nPending Intake (_inbox/):")
for item in sorted(inbox_items):
has_config = (item / "intake.yaml").exists()
has_model = bool(list(item.glob("**/*.sim")))
status = []
if has_config:
status.append("yaml")
if has_model:
status.append("model")
print(f" {item.name:<30} [{', '.join(status) or 'empty'}]")
# Active studies
print("\nStudies:")
for study_dir in sorted(studies_dir.iterdir()):
if (
study_dir.is_dir()
and not study_dir.name.startswith("_")
and not study_dir.name.startswith(".")
):
has_spec = (study_dir / "atomizer_spec.json").exists() or (
study_dir / "optimization_config.json"
).exists()
has_db = any(study_dir.rglob("study.db"))
has_approval = (study_dir / ".validation_approved").exists()
status = []
if has_spec:
status.append("configured")
if has_approval:
status.append("approved")
if has_db:
status.append("has_data")
print(f" {study_dir.name:<30} [{', '.join(status) or 'new'}]")
return 0
def main():
parser = argparse.ArgumentParser(
description="Atomizer - Neural-Accelerated Structural Optimization",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Complete neural workflow
python atomizer.py neural-optimize --study my_study --trials 500
# Create study from template
python atomizer.py create-study --template beam_stiffness --name my_beam
# Check status
python atomizer.py status --study my_study
# Manual training
python atomizer.py train --study my_study --epochs 100
""",
)
parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")
subparsers = parser.add_subparsers(dest="command", help="Commands")
# neural-optimize command
neural_parser = subparsers.add_parser(
"neural-optimize", help="Run neural-accelerated optimization (main workflow)"
)
neural_parser.add_argument("--study", "-s", required=True, help="Study name")
neural_parser.add_argument("--trials", "-n", type=int, default=500, help="Total trials")
neural_parser.add_argument("--min-points", type=int, default=50, help="Min points for training")
neural_parser.add_argument(
"--retrain-every", type=int, default=50, help="Retrain after N new points"
)
neural_parser.add_argument("--epochs", type=int, default=100, help="Training epochs")
neural_parser.add_argument("--resume", action="store_true", help="Resume existing study")
# create-study command
create_parser = subparsers.add_parser("create-study", help="Create study from template")
create_parser.add_argument("--template", "-t", required=True, help="Template name")
create_parser.add_argument("--name", "-n", required=True, help="Study name")
# list-templates command
list_parser = subparsers.add_parser("list-templates", help="List available templates")
# status command
status_parser = subparsers.add_parser("status", help="Show status")
status_parser.add_argument("--study", "-s", help="Study name (omit for all)")
# train command
train_parser = subparsers.add_parser("train", help="Train neural model")
train_parser.add_argument("--study", "-s", required=True, help="Study name")
train_parser.add_argument("--epochs", type=int, default=100, help="Training epochs")
train_parser.add_argument("--min-points", type=int, default=50, help="Min points threshold")
train_parser.add_argument("--force", action="store_true", help="Force training")
# validate command
validate_parser = subparsers.add_parser("validate", help="Validate study setup")
validate_parser.add_argument("--study", "-s", required=True, help="Study name")
# ========================================================================
# NEW UX SYSTEM COMMANDS
# ========================================================================
# intake command
intake_parser = subparsers.add_parser("intake", help="Process an intake folder into a study")
intake_parser.add_argument("folder", help="Path to intake folder")
intake_parser.add_argument("--skip-baseline", action="store_true", help="Skip baseline solve")
# gate command (validation gate)
gate_parser = subparsers.add_parser("gate", help="Run validation gate with test trials")
gate_parser.add_argument("study", help="Study name or path")
gate_parser.add_argument("--skip-trials", action="store_true", help="Skip test trials")
gate_parser.add_argument("--trials", type=int, default=3, help="Number of test trials")
gate_parser.add_argument("--approve", action="store_true", help="Approve if validation passes")
# list command
list_studies_parser = subparsers.add_parser("list", help="List all studies and inbox items")
# finalize command
finalize_parser = subparsers.add_parser("finalize", help="Generate final HTML report")
finalize_parser.add_argument("study", help="Study name or path")
finalize_parser.add_argument("--pdf", action="store_true", help="Also generate PDF")
finalize_parser.add_argument("--open", action="store_true", help="Open report in browser")
args = parser.parse_args()
if not args.command:
parser.print_help()
return 0
setup_logging(args.verbose)
# Dispatch to command handler
commands = {
"neural-optimize": cmd_neural_optimize,
"create-study": cmd_create_study,
"list-templates": cmd_list_templates,
"status": cmd_status,
"train": cmd_train,
"validate": cmd_validate,
# New UX commands
"intake": cmd_intake,
"gate": cmd_gate,
"list": cmd_list_studies,
"finalize": cmd_finalize,
}
handler = commands.get(args.command)
if handler:
return handler(args)
else:
parser.print_help()
return 1
if __name__ == "__main__":
sys.exit(main())