""" 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()