docs: Complete M1 mirror optimization campaign V11-V15
## M1 Mirror Campaign Summary - V11-V15 optimization campaign completed (~1,400 FEA evaluations) - Best design: V14 Trial #725 with Weighted Sum = 121.72 - V15 NSGA-II confirmed V14 TPE found optimal solution - Campaign improved from WS=129.33 (V11) to WS=121.72 (V14): -5.9% ## Key Results - 40° tracking: 5.99 nm (target 4.0 nm) - 60° tracking: 13.10 nm (target 10.0 nm) - Manufacturing: 26.28 nm (target 20.0 nm) - Targets not achievable within current design space ## Documentation Added - V15 STUDY_REPORT.md: Detailed NSGA-II results analysis - M1_MIRROR_CAMPAIGN_SUMMARY.md: Full V11-V15 campaign overview - Updated CLAUDE.md, ATOMIZER_CONTEXT.md with NXSolver patterns - Updated 01_CHEATSHEET.md with --resume guidance - Updated OP_01_CREATE_STUDY.md with FEARunner template ## Studies Added - m1_mirror_adaptive_V13: TPE validation (291 trials) - m1_mirror_adaptive_V14: TPE intensive (785 trials, BEST) - m1_mirror_adaptive_V15: NSGA-II exploration (126 new FEA) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -197,62 +197,132 @@ Create `optimization_config.json`:
|
||||
|
||||
### Step 6: Generate run_optimization.py
|
||||
|
||||
**CRITICAL**: Always use the `FEARunner` class pattern with proper `NXSolver` initialization.
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
{study_name} - Optimization Runner
|
||||
Generated by Atomizer LLM
|
||||
"""
|
||||
import sys
|
||||
import re
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Dict, Optional, Any
|
||||
|
||||
# Add optimization engine to path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
||||
|
||||
import optuna
|
||||
from optimization_engine.nx_solver import NXSolver
|
||||
from optimization_engine.extractors import extract_displacement, extract_solid_stress
|
||||
from optimization_engine.utils import ensure_nx_running
|
||||
from optimization_engine.extractors import extract_solid_stress
|
||||
|
||||
# Paths
|
||||
STUDY_DIR = Path(__file__).parent
|
||||
MODEL_DIR = STUDY_DIR / "1_setup" / "model"
|
||||
RESULTS_DIR = STUDY_DIR / "2_results"
|
||||
SETUP_DIR = STUDY_DIR / "1_setup"
|
||||
ITERATIONS_DIR = STUDY_DIR / "2_iterations"
|
||||
RESULTS_DIR = STUDY_DIR / "3_results"
|
||||
CONFIG_PATH = SETUP_DIR / "optimization_config.json"
|
||||
|
||||
def objective(trial):
|
||||
"""Optimization objective function."""
|
||||
# Sample design variables
|
||||
thickness = trial.suggest_float("thickness", 2.0, 10.0)
|
||||
# Ensure directories exist
|
||||
ITERATIONS_DIR.mkdir(exist_ok=True)
|
||||
RESULTS_DIR.mkdir(exist_ok=True)
|
||||
|
||||
# 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")
|
||||
class FEARunner:
|
||||
"""Runs actual FEA simulations. Always use this pattern!"""
|
||||
|
||||
# Extract results using library extractors
|
||||
op2_file = result['op2_file']
|
||||
stress_result = extract_solid_stress(op2_file)
|
||||
max_stress = stress_result['max_von_mises']
|
||||
def __init__(self, config: Dict[str, Any]):
|
||||
self.config = config
|
||||
self.nx_solver = None
|
||||
self.nx_manager = None
|
||||
self.master_model_dir = SETUP_DIR / "model"
|
||||
|
||||
# Check constraint
|
||||
if max_stress > 250.0:
|
||||
raise optuna.TrialPruned(f"Stress constraint violated: {max_stress} MPa")
|
||||
def setup(self):
|
||||
"""Setup NX and solver. Called lazily on first use."""
|
||||
study_name = self.config.get('study_name', 'my_study')
|
||||
|
||||
# Return objective
|
||||
mass = extract_mass(...)
|
||||
return mass
|
||||
# Ensure NX is running
|
||||
self.nx_manager, nx_was_started = ensure_nx_running(
|
||||
session_id=study_name,
|
||||
auto_start=True,
|
||||
start_timeout=120
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run optimization
|
||||
import optuna
|
||||
study = optuna.create_study(direction="minimize")
|
||||
study.optimize(objective, n_trials=50)
|
||||
# CRITICAL: Initialize NXSolver with named parameters, NOT config dict
|
||||
nx_settings = self.config.get('nx_settings', {})
|
||||
nx_install_dir = nx_settings.get('nx_install_path', 'C:\\Program Files\\Siemens\\NX2506')
|
||||
|
||||
# Extract version from path
|
||||
version_match = re.search(r'NX(\d+)', nx_install_dir)
|
||||
nastran_version = version_match.group(1) if version_match else "2506"
|
||||
|
||||
self.nx_solver = NXSolver(
|
||||
master_model_dir=str(self.master_model_dir),
|
||||
nx_install_dir=nx_install_dir,
|
||||
nastran_version=nastran_version,
|
||||
timeout=nx_settings.get('simulation_timeout_s', 600),
|
||||
use_iteration_folders=True,
|
||||
study_name=study_name
|
||||
)
|
||||
|
||||
def run_fea(self, params: Dict[str, float], iter_num: int) -> Optional[Dict]:
|
||||
"""Run FEA simulation and extract results."""
|
||||
if self.nx_solver is None:
|
||||
self.setup()
|
||||
|
||||
# Create expression updates
|
||||
expressions = {var['expression_name']: params[var['name']]
|
||||
for var in self.config['design_variables']}
|
||||
|
||||
# Create iteration folder with model copies
|
||||
iter_folder = self.nx_solver.create_iteration_folder(
|
||||
iterations_base_dir=ITERATIONS_DIR,
|
||||
iteration_number=iter_num,
|
||||
expression_updates=expressions
|
||||
)
|
||||
|
||||
# Run simulation
|
||||
nx_settings = self.config.get('nx_settings', {})
|
||||
sim_file = iter_folder / nx_settings.get('sim_file', 'model.sim')
|
||||
|
||||
result = self.nx_solver.run_simulation(
|
||||
sim_file=sim_file,
|
||||
working_dir=iter_folder,
|
||||
expression_updates=expressions,
|
||||
solution_name=nx_settings.get('solution_name', 'Solution 1'),
|
||||
cleanup=False
|
||||
)
|
||||
|
||||
if not result['success']:
|
||||
return None
|
||||
|
||||
# Extract results
|
||||
op2_file = result['op2_file']
|
||||
stress_result = extract_solid_stress(op2_file)
|
||||
|
||||
return {
|
||||
'params': params,
|
||||
'max_stress': stress_result['max_von_mises'],
|
||||
'op2_file': op2_file
|
||||
}
|
||||
|
||||
|
||||
# Optimizer class would use FEARunner...
|
||||
# See m1_mirror_adaptive_V14/run_optimization.py for full example
|
||||
```
|
||||
|
||||
**WRONG** - causes `TypeError: expected str, bytes or os.PathLike object, not dict`:
|
||||
```python
|
||||
self.nx_solver = NXSolver(self.config) # ❌ NEVER DO THIS
|
||||
```
|
||||
|
||||
**Reference implementations**:
|
||||
- `studies/m1_mirror_adaptive_V14/run_optimization.py` (TPE single-objective)
|
||||
- `studies/m1_mirror_adaptive_V15/run_optimization.py` (NSGA-II multi-objective)
|
||||
|
||||
### Step 7: Generate Documentation
|
||||
|
||||
**README.md** (11 sections required):
|
||||
@@ -400,4 +470,5 @@ Generated config:
|
||||
|
||||
| Version | Date | Changes |
|
||||
|---------|------|---------|
|
||||
| 1.1 | 2025-12-12 | Added FEARunner class pattern, NXSolver initialization warning |
|
||||
| 1.0 | 2025-12-05 | Initial release |
|
||||
|
||||
Reference in New Issue
Block a user