Files
Atomizer/docs/protocols/operations/OP_01_CREATE_STUDY.md
Antoine 01a7d7d121 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>
2025-12-16 14:55:23 -05:00

13 KiB

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:

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, SYS_11_MULTI_OBJECTIVE.

Step 4: Select Extractors

Match physics to extractors from SYS_12_EXTRACTOR_LIBRARY:

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:

{
  "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

CRITICAL: Always use the FEARunner class pattern with proper NXSolver initialization.

#!/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.utils import ensure_nx_running
from optimization_engine.extractors import extract_solid_stress

# Paths
STUDY_DIR = Path(__file__).parent
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"

# Ensure directories exist
ITERATIONS_DIR.mkdir(exist_ok=True)
RESULTS_DIR.mkdir(exist_ok=True)


class FEARunner:
    """Runs actual FEA simulations. Always use this pattern!"""

    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"

    def setup(self):
        """Setup NX and solver. Called lazily on first use."""
        study_name = self.config.get('study_name', 'my_study')

        # Ensure NX is running
        self.nx_manager, nx_was_started = ensure_nx_running(
            session_id=study_name,
            auto_start=True,
            start_timeout=120
        )

        # 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:

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):

  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):

# 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


Version History

Version Date Changes
1.1 2025-12-12 Added FEARunner class pattern, NXSolver initialization warning
1.0 2025-12-05 Initial release