# OP_01: Create Optimization Study ## Overview This protocol guides you through creating a complete Atomizer optimization study from scratch. It covers gathering requirements, generating configuration files, and validating setup. **Skill to Load**: `.claude/skills/core/study-creation-core.md` --- ## When to Use | Trigger | Action | |---------|--------| | "new study", "create study" | Follow this protocol | | "set up optimization" | Follow this protocol | | "optimize my design" | Follow this protocol | | User provides NX model | Assess and follow this protocol | --- ## Quick Reference **Required Outputs**: | File | Purpose | Location | |------|---------|----------| | `optimization_config.json` | Design vars, objectives, constraints | `1_setup/` | | `run_optimization.py` | Execution script | Study root | | `README.md` | Engineering documentation | Study root | | `STUDY_REPORT.md` | Results template | Study root | **Study Structure**: ``` studies/{study_name}/ ├── 1_setup/ │ ├── model/ # NX files (.prt, .sim, .fem) │ └── optimization_config.json ├── 2_results/ # Created during run ├── README.md # MANDATORY ├── STUDY_REPORT.md # MANDATORY └── run_optimization.py ``` --- ## Detailed Steps ### Step 1: Gather Requirements **Ask the user**: 1. What are you trying to optimize? (objective) 2. What can you change? (design variables) 3. What limits must be respected? (constraints) 4. Where are your NX files? **Example Dialog**: ``` User: "I want to optimize my bracket" You: "What should I optimize for - minimum mass, maximum stiffness, target frequency, or something else?" User: "Minimize mass while keeping stress below 250 MPa" ``` ### Step 2: Analyze Model (Introspection) **MANDATORY**: When user provides NX files, run comprehensive introspection: ```python from optimization_engine.hooks.nx_cad.model_introspection import ( introspect_part, introspect_simulation, introspect_op2, introspect_study ) # Introspect the part file to get expressions, mass, features part_info = introspect_part("C:/path/to/model.prt") # Introspect the simulation to get solutions, BCs, loads sim_info = introspect_simulation("C:/path/to/model.sim") # If OP2 exists, check what results are available op2_info = introspect_op2("C:/path/to/results.op2") # Or introspect entire study directory at once study_info = introspect_study("studies/my_study/") ``` **Introspection Report Contents**: | Source | Information Extracted | |--------|----------------------| | `.prt` | Expressions (count, values, types), bodies, mass, material, features | | `.sim` | Solutions, boundary conditions, loads, materials, mesh info, output requests | | `.op2` | Available results (displacement, stress, strain, SPC forces, etc.), subcases | **Generate Introspection Report** at study creation: 1. Save report to `studies/{study_name}/MODEL_INTROSPECTION.md` 2. Include summary of what's available for optimization 3. List potential design variables (expressions) 4. List extractable results (from OP2) **Key Questions Answered by Introspection**: - What expressions exist? (potential design variables) - What solution types? (static, modal, etc.) - What results are available in OP2? (displacement, stress, SPC forces) - Multi-solution required? (static + modal = set `solution_name=None`) ### Step 3: Select Protocol Based on objectives: | Scenario | Protocol | Sampler | |----------|----------|---------| | Single objective | Protocol 10 (IMSO) | TPE, CMA-ES, or GP | | 2-3 objectives | Protocol 11 | NSGA-II | | >50 trials, need speed | Protocol 14 | + Neural acceleration | See [SYS_10_IMSO](../system/SYS_10_IMSO.md), [SYS_11_MULTI_OBJECTIVE](../system/SYS_11_MULTI_OBJECTIVE.md). ### Step 4: Select Extractors Match physics to extractors from [SYS_12_EXTRACTOR_LIBRARY](../system/SYS_12_EXTRACTOR_LIBRARY.md): | Need | Extractor ID | Function | |------|--------------|----------| | Max displacement | E1 | `extract_displacement()` | | Natural frequency | E2 | `extract_frequency()` | | Von Mises stress | E3 | `extract_solid_stress()` | | Mass from BDF | E4 | `extract_mass_from_bdf()` | | Mass from NX | E5 | `extract_mass_from_expression()` | | Wavefront error | E8-E10 | Zernike extractors | ### Step 5: Generate Configuration Create `optimization_config.json`: ```json { "study_name": "bracket_optimization", "description": "Minimize bracket mass while meeting stress constraint", "design_variables": [ { "name": "thickness", "type": "continuous", "min": 2.0, "max": 10.0, "unit": "mm", "description": "Wall thickness" } ], "objectives": [ { "name": "mass", "type": "minimize", "unit": "kg", "description": "Total bracket mass" } ], "constraints": [ { "name": "max_stress", "type": "less_than", "value": 250.0, "unit": "MPa", "description": "Maximum allowable von Mises stress" } ], "simulation": { "model_file": "1_setup/model/bracket.prt", "sim_file": "1_setup/model/bracket.sim", "solver": "nastran", "solution_name": null }, "optimization_settings": { "protocol": "protocol_10_single_objective", "sampler": "TPESampler", "n_trials": 50 } } ``` ### Step 6: Generate run_optimization.py ```python #!/usr/bin/env python """ {study_name} - Optimization Runner Generated by Atomizer LLM """ import sys from pathlib import Path # Add optimization engine to path sys.path.insert(0, str(Path(__file__).parent.parent.parent)) from optimization_engine.nx_solver import NXSolver from optimization_engine.extractors import extract_displacement, extract_solid_stress # Paths STUDY_DIR = Path(__file__).parent MODEL_DIR = STUDY_DIR / "1_setup" / "model" RESULTS_DIR = STUDY_DIR / "2_results" def objective(trial): """Optimization objective function.""" # Sample design variables thickness = trial.suggest_float("thickness", 2.0, 10.0) # Update NX model and solve nx_solver = NXSolver(...) result = nx_solver.run_simulation( sim_file=MODEL_DIR / "bracket.sim", working_dir=MODEL_DIR, expression_updates={"thickness": thickness} ) if not result['success']: raise optuna.TrialPruned("Simulation failed") # Extract results using library extractors op2_file = result['op2_file'] stress_result = extract_solid_stress(op2_file) max_stress = stress_result['max_von_mises'] # Check constraint if max_stress > 250.0: raise optuna.TrialPruned(f"Stress constraint violated: {max_stress} MPa") # Return objective mass = extract_mass(...) return mass if __name__ == "__main__": # Run optimization import optuna study = optuna.create_study(direction="minimize") study.optimize(objective, n_trials=50) ``` ### Step 7: Generate Documentation **README.md** (11 sections required): 1. Engineering Problem 2. Mathematical Formulation 3. Optimization Algorithm 4. Simulation Pipeline 5. Result Extraction Methods 6. Neural Acceleration (if applicable) 7. Study File Structure 8. Results Location 9. Quick Start 10. Configuration Reference 11. References **STUDY_REPORT.md** (template): ```markdown # Study Report: {study_name} ## Executive Summary - Trials completed: _pending_ - Best objective: _pending_ - Constraint satisfaction: _pending_ ## Optimization Progress _To be filled after run_ ## Best Designs Found _To be filled after run_ ## Recommendations _To be filled after analysis_ ``` ### Step 8: Validate NX Model File Chain **CRITICAL**: NX simulation files have parent-child dependencies. ALL linked files must be copied to the study folder. **Required File Chain Check**: ``` .sim (Simulation) └── .fem (FEM) └── _i.prt (Idealized Part) ← OFTEN MISSING! └── .prt (Geometry Part) ``` **Validation Steps**: 1. Open the `.sim` file in NX 2. Go to **Assemblies → Assembly Navigator** or check **Part Navigator** 3. Identify ALL child components (especially `*_i.prt` idealized parts) 4. Copy ALL linked files to `1_setup/model/` **Common Issue**: The `_i.prt` (idealized part) is often forgotten. Without it: - `UpdateFemodel()` runs but mesh doesn't change - Geometry changes don't propagate to FEM - All optimization trials produce identical results **File Checklist**: | File Pattern | Description | Required | |--------------|-------------|----------| | `*.prt` | Geometry part | ✅ Always | | `*_i.prt` | Idealized part | ✅ If FEM uses idealization | | `*.fem` | FEM file | ✅ Always | | `*.sim` | Simulation file | ✅ Always | **Introspection should report**: - List of all parts referenced by .sim - Warning if any referenced parts are missing from study folder ### Step 9: Final Validation Checklist Before running: - [ ] NX files exist in `1_setup/model/` - [ ] **ALL child parts copied** (especially `*_i.prt`) - [ ] Expression names match model - [ ] Config validates (JSON schema) - [ ] `run_optimization.py` has no syntax errors - [ ] README.md has all 11 sections - [ ] STUDY_REPORT.md template exists --- ## Examples ### Example 1: Simple Bracket ``` User: "Optimize my bracket.prt for minimum mass, stress < 250 MPa" Generated config: - 1 design variable (thickness) - 1 objective (minimize mass) - 1 constraint (stress < 250) - Protocol 10, TPE sampler - 50 trials ``` ### Example 2: Multi-Objective Beam ``` User: "Minimize mass AND maximize stiffness for my beam" Generated config: - 2 design variables (width, height) - 2 objectives (minimize mass, maximize stiffness) - Protocol 11, NSGA-II sampler - 50 trials (Pareto front) ``` ### Example 3: Telescope Mirror ``` User: "Minimize wavefront error at 40deg vs 20deg reference" Generated config: - Multiple design variables (mount positions) - 1 objective (minimize relative WFE) - Zernike extractor E9 - Protocol 10 ``` --- ## Troubleshooting | Symptom | Cause | Solution | |---------|-------|----------| | "Expression not found" | Name mismatch | Verify expression names in NX | | "No feasible designs" | Constraints too tight | Relax constraint values | | Config validation fails | Missing required field | Check JSON schema | | Import error | Wrong path | Check sys.path setup | --- ## Cross-References - **Depends On**: [SYS_12_EXTRACTOR_LIBRARY](../system/SYS_12_EXTRACTOR_LIBRARY.md) - **Next Step**: [OP_02_RUN_OPTIMIZATION](./OP_02_RUN_OPTIMIZATION.md) - **Skill**: `.claude/skills/core/study-creation-core.md` --- ## Version History | Version | Date | Changes | |---------|------|---------| | 1.0 | 2025-12-05 | Initial release |