feat: Add neural loop automation - templates, auto-trainer, CLI
Closes the neural training loop with automated workflow: - atomizer.py: One-command neural workflow CLI - auto_trainer.py: Auto-training trigger system (50pt threshold) - template_loader.py: Study creation from templates - study_reset.py: Study reset/cleanup utility - 3 templates: beam stiffness, bracket stress, frequency tuning - State assessment document (Nov 25) Usage: python atomizer.py neural-optimize --study my_study --trials 500 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
383
optimization_engine/template_loader.py
Normal file
383
optimization_engine/template_loader.py
Normal file
@@ -0,0 +1,383 @@
|
||||
"""
|
||||
Template Loader for Atomizer Optimization Studies
|
||||
|
||||
Creates new studies from templates with automatic folder structure creation.
|
||||
|
||||
Usage:
|
||||
from optimization_engine.template_loader import create_study_from_template, list_templates
|
||||
|
||||
# List available templates
|
||||
templates = list_templates()
|
||||
|
||||
# Create a new study from template
|
||||
create_study_from_template(
|
||||
template_name="beam_stiffness_optimization",
|
||||
study_name="my_beam_study"
|
||||
)
|
||||
"""
|
||||
|
||||
import json
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, List, Optional
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
TEMPLATES_DIR = Path(__file__).parent.parent / "templates"
|
||||
STUDIES_DIR = Path(__file__).parent.parent / "studies"
|
||||
|
||||
|
||||
def list_templates() -> List[Dict[str, Any]]:
|
||||
"""
|
||||
List all available templates.
|
||||
|
||||
Returns:
|
||||
List of template metadata dictionaries
|
||||
"""
|
||||
templates = []
|
||||
|
||||
if not TEMPLATES_DIR.exists():
|
||||
return templates
|
||||
|
||||
for template_file in TEMPLATES_DIR.glob("*.json"):
|
||||
try:
|
||||
with open(template_file, 'r') as f:
|
||||
config = json.load(f)
|
||||
|
||||
template_info = config.get("template_info", {})
|
||||
templates.append({
|
||||
"name": template_file.stem,
|
||||
"description": config.get("description", "No description"),
|
||||
"category": template_info.get("category", "general"),
|
||||
"analysis_type": template_info.get("analysis_type", "unknown"),
|
||||
"objectives": len(config.get("objectives", [])),
|
||||
"design_variables": len(config.get("design_variables", [])),
|
||||
"path": str(template_file)
|
||||
})
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not load template {template_file}: {e}")
|
||||
|
||||
return templates
|
||||
|
||||
|
||||
def get_template(template_name: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Load a template by name.
|
||||
|
||||
Args:
|
||||
template_name: Name of the template (without .json extension)
|
||||
|
||||
Returns:
|
||||
Template configuration dictionary or None if not found
|
||||
"""
|
||||
template_path = TEMPLATES_DIR / f"{template_name}.json"
|
||||
|
||||
if not template_path.exists():
|
||||
# Try with .json extension already included
|
||||
template_path = TEMPLATES_DIR / template_name
|
||||
if not template_path.exists():
|
||||
return None
|
||||
|
||||
with open(template_path, 'r') as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def create_study_from_template(
|
||||
template_name: str,
|
||||
study_name: str,
|
||||
studies_dir: Optional[Path] = None,
|
||||
overrides: Optional[Dict[str, Any]] = None
|
||||
) -> Path:
|
||||
"""
|
||||
Create a new study from a template.
|
||||
|
||||
Args:
|
||||
template_name: Name of the template to use
|
||||
study_name: Name for the new study
|
||||
studies_dir: Base directory for studies (default: studies/)
|
||||
overrides: Dictionary of config values to override
|
||||
|
||||
Returns:
|
||||
Path to the created study directory
|
||||
|
||||
Raises:
|
||||
FileNotFoundError: If template doesn't exist
|
||||
FileExistsError: If study already exists
|
||||
"""
|
||||
if studies_dir is None:
|
||||
studies_dir = STUDIES_DIR
|
||||
|
||||
studies_dir = Path(studies_dir)
|
||||
|
||||
# Load template
|
||||
template = get_template(template_name)
|
||||
if template is None:
|
||||
available = [t["name"] for t in list_templates()]
|
||||
raise FileNotFoundError(
|
||||
f"Template '{template_name}' not found. "
|
||||
f"Available templates: {available}"
|
||||
)
|
||||
|
||||
# Check if study already exists
|
||||
study_path = studies_dir / study_name
|
||||
if study_path.exists():
|
||||
raise FileExistsError(
|
||||
f"Study '{study_name}' already exists at {study_path}. "
|
||||
"Choose a different name or delete the existing study."
|
||||
)
|
||||
|
||||
# Create study directory structure
|
||||
setup_dir = study_path / "1_setup"
|
||||
model_dir = setup_dir / "model"
|
||||
results_dir = study_path / "2_results"
|
||||
|
||||
setup_dir.mkdir(parents=True)
|
||||
model_dir.mkdir()
|
||||
results_dir.mkdir()
|
||||
|
||||
# Customize template for this study
|
||||
config = template.copy()
|
||||
config["study_name"] = study_name
|
||||
config["created_from_template"] = template_name
|
||||
config["created_at"] = datetime.now().isoformat()
|
||||
|
||||
# Update training data export path
|
||||
if "training_data_export" in config:
|
||||
export_dir = config["training_data_export"].get("export_dir", "")
|
||||
if "${study_name}" in export_dir:
|
||||
config["training_data_export"]["export_dir"] = export_dir.replace(
|
||||
"${study_name}", study_name
|
||||
)
|
||||
|
||||
# Apply overrides
|
||||
if overrides:
|
||||
_deep_update(config, overrides)
|
||||
|
||||
# Write configuration
|
||||
config_path = setup_dir / "optimization_config.json"
|
||||
with open(config_path, 'w') as f:
|
||||
json.dump(config, f, indent=2)
|
||||
|
||||
# Create run_optimization.py
|
||||
run_script_content = _generate_run_script(study_name, config)
|
||||
run_script_path = study_path / "run_optimization.py"
|
||||
with open(run_script_path, 'w') as f:
|
||||
f.write(run_script_content)
|
||||
|
||||
# Create README.md
|
||||
readme_content = _generate_study_readme(study_name, config, template_name)
|
||||
readme_path = study_path / "README.md"
|
||||
with open(readme_path, 'w') as f:
|
||||
f.write(readme_content)
|
||||
|
||||
print(f"Created study '{study_name}' from template '{template_name}'")
|
||||
print(f" Location: {study_path}")
|
||||
print(f" Config: {config_path}")
|
||||
print(f"\nNext steps:")
|
||||
print(f" 1. Add your NX model files to: {model_dir}")
|
||||
print(f" 2. Update design variable bounds in optimization_config.json")
|
||||
print(f" 3. Run: python {run_script_path} --trials 50")
|
||||
|
||||
return study_path
|
||||
|
||||
|
||||
def _deep_update(base: Dict, updates: Dict) -> Dict:
|
||||
"""Recursively update a dictionary."""
|
||||
for key, value in updates.items():
|
||||
if key in base and isinstance(base[key], dict) and isinstance(value, dict):
|
||||
_deep_update(base[key], value)
|
||||
else:
|
||||
base[key] = value
|
||||
return base
|
||||
|
||||
|
||||
def _generate_run_script(study_name: str, config: Dict[str, Any]) -> str:
|
||||
"""Generate the run_optimization.py script for a study."""
|
||||
return f'''"""
|
||||
Optimization Runner for {study_name}
|
||||
|
||||
Auto-generated from template: {config.get('created_from_template', 'unknown')}
|
||||
Created: {config.get('created_at', 'unknown')}
|
||||
|
||||
Usage:
|
||||
python run_optimization.py --trials 50
|
||||
python run_optimization.py --trials 25 --resume
|
||||
python run_optimization.py --trials 100 --enable-nn
|
||||
"""
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
# Add project root to path
|
||||
project_root = Path(__file__).parent.parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from optimization_engine.study_runner import run_study
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="{config.get('description', study_name)}")
|
||||
parser.add_argument('--trials', type=int, default=30, help='Number of trials to run')
|
||||
parser.add_argument('--resume', action='store_true', help='Resume existing study')
|
||||
parser.add_argument('--enable-nn', action='store_true', help='Enable neural network acceleration')
|
||||
parser.add_argument('--validate-only', action='store_true', help='Only validate setup, do not run')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
study_dir = Path(__file__).parent
|
||||
config_path = study_dir / "1_setup" / "optimization_config.json"
|
||||
|
||||
if args.validate_only:
|
||||
from optimization_engine.validators import validate_study
|
||||
result = validate_study("{study_name}")
|
||||
print(result)
|
||||
return
|
||||
|
||||
run_study(
|
||||
config_path=config_path,
|
||||
n_trials=args.trials,
|
||||
resume=args.resume,
|
||||
enable_neural=args.enable_nn
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
'''
|
||||
|
||||
|
||||
def _generate_study_readme(study_name: str, config: Dict[str, Any], template_name: str) -> str:
|
||||
"""Generate a README.md for the study."""
|
||||
objectives = config.get("objectives", [])
|
||||
design_vars = config.get("design_variables", [])
|
||||
constraints = config.get("constraints", [])
|
||||
|
||||
obj_list = "\n".join([f"- **{o.get('name', 'unnamed')}**: {o.get('goal', 'minimize')} - {o.get('description', '')}" for o in objectives])
|
||||
dv_list = "\n".join([f"- **{d.get('parameter', 'unnamed')}**: [{d.get('bounds', [0, 1])[0]}, {d.get('bounds', [0, 1])[1]}] - {d.get('description', '')}" for d in design_vars])
|
||||
const_list = "\n".join([f"- **{c.get('name', 'unnamed')}**: {c.get('type', 'less_than')} {c.get('threshold', 0)} - {c.get('description', '')}" for c in constraints])
|
||||
|
||||
return f'''# {study_name}
|
||||
|
||||
{config.get('description', 'Optimization study')}
|
||||
|
||||
**Template**: {template_name}
|
||||
**Created**: {config.get('created_at', 'unknown')}
|
||||
|
||||
## Engineering Context
|
||||
|
||||
{config.get('engineering_context', 'No context provided')}
|
||||
|
||||
## Objectives
|
||||
|
||||
{obj_list if obj_list else 'None defined'}
|
||||
|
||||
## Design Variables
|
||||
|
||||
{dv_list if dv_list else 'None defined'}
|
||||
|
||||
## Constraints
|
||||
|
||||
{const_list if const_list else 'None defined'}
|
||||
|
||||
## Setup Instructions
|
||||
|
||||
1. **Add NX Model Files**
|
||||
|
||||
Copy your NX part (.prt), simulation (.sim), and FEM (.fem) files to:
|
||||
```
|
||||
1_setup/model/
|
||||
```
|
||||
|
||||
2. **Configure Design Variables**
|
||||
|
||||
Edit `1_setup/optimization_config.json`:
|
||||
- Ensure `design_variables[].parameter` matches your NX expression names
|
||||
- Adjust bounds to your design space
|
||||
|
||||
3. **Validate Setup**
|
||||
|
||||
```bash
|
||||
python run_optimization.py --validate-only
|
||||
```
|
||||
|
||||
## Running the Optimization
|
||||
|
||||
### Basic Run
|
||||
```bash
|
||||
python run_optimization.py --trials 50
|
||||
```
|
||||
|
||||
### Resume Interrupted Run
|
||||
```bash
|
||||
python run_optimization.py --trials 25 --resume
|
||||
```
|
||||
|
||||
### With Neural Network Acceleration
|
||||
```bash
|
||||
python run_optimization.py --trials 100 --enable-nn
|
||||
```
|
||||
|
||||
## Results
|
||||
|
||||
After optimization, results are saved in `2_results/`:
|
||||
- `study.db` - Optuna database with all trials
|
||||
- `history.json` - Trial history
|
||||
- `optimization_summary.json` - Summary with best parameters
|
||||
|
||||
## Visualization
|
||||
|
||||
View results with Optuna Dashboard:
|
||||
```bash
|
||||
optuna-dashboard sqlite:///2_results/study.db
|
||||
```
|
||||
|
||||
Or generate a report:
|
||||
```bash
|
||||
python -m optimization_engine.generate_report {study_name}
|
||||
```
|
||||
'''
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="Atomizer Template Loader")
|
||||
subparsers = parser.add_subparsers(dest="command", help="Commands")
|
||||
|
||||
# List templates
|
||||
list_parser = subparsers.add_parser("list", help="List available templates")
|
||||
|
||||
# Create study
|
||||
create_parser = subparsers.add_parser("create", 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")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command == "list":
|
||||
templates = list_templates()
|
||||
if not templates:
|
||||
print("No templates found in templates/")
|
||||
else:
|
||||
print("Available templates:")
|
||||
print("-" * 60)
|
||||
for t in templates:
|
||||
print(f" {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()
|
||||
|
||||
elif args.command == "create":
|
||||
try:
|
||||
study_path = create_study_from_template(
|
||||
template_name=args.template,
|
||||
study_name=args.name
|
||||
)
|
||||
except (FileNotFoundError, FileExistsError) as e:
|
||||
print(f"Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
else:
|
||||
parser.print_help()
|
||||
Reference in New Issue
Block a user