feat: Add MLP surrogate with Turbo Mode for 100x faster optimization
Neural Acceleration (MLP Surrogate): - Add run_nn_optimization.py with hybrid FEA/NN workflow - MLP architecture: 4-layer (64->128->128->64) with BatchNorm/Dropout - Three workflow modes: - --all: Sequential export->train->optimize->validate - --hybrid-loop: Iterative Train->NN->Validate->Retrain cycle - --turbo: Aggressive single-best validation (RECOMMENDED) - Turbo mode: 5000 NN trials + 50 FEA validations in ~12 minutes - Separate nn_study.db to avoid overloading dashboard Performance Results (bracket_pareto_3obj study): - NN prediction errors: mass 1-5%, stress 1-4%, stiffness 5-15% - Found minimum mass designs at boundary (angle~30deg, thick~30mm) - 100x speedup vs pure FEA exploration Protocol Operating System: - Add .claude/skills/ with Bootstrap, Cheatsheet, Context Loader - Add docs/protocols/ with operations (OP_01-06) and system (SYS_10-14) - Update SYS_14_NEURAL_ACCELERATION.md with MLP Turbo Mode docs NX Automation: - Add optimization_engine/hooks/ for NX CAD/CAE automation - Add study_wizard.py for guided study creation - Fix FEM mesh update: load idealized part before UpdateFemodel() New Study: - bracket_pareto_3obj: 3-objective Pareto (mass, stress, stiffness) - 167 FEA trials + 5000 NN trials completed - Demonstrates full hybrid workflow 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
395
docs/protocols/extensions/EXT_01_CREATE_EXTRACTOR.md
Normal file
395
docs/protocols/extensions/EXT_01_CREATE_EXTRACTOR.md
Normal file
@@ -0,0 +1,395 @@
|
||||
# EXT_01: Create New Extractor
|
||||
|
||||
<!--
|
||||
PROTOCOL: Create New Physics Extractor
|
||||
LAYER: Extensions
|
||||
VERSION: 1.0
|
||||
STATUS: Active
|
||||
LAST_UPDATED: 2025-12-05
|
||||
PRIVILEGE: power_user
|
||||
LOAD_WITH: [SYS_12_EXTRACTOR_LIBRARY]
|
||||
-->
|
||||
|
||||
## Overview
|
||||
|
||||
This protocol guides you through creating a new physics extractor for the centralized extractor library. Follow this when you need to extract results not covered by existing extractors.
|
||||
|
||||
**Privilege Required**: power_user or admin
|
||||
|
||||
---
|
||||
|
||||
## When to Use
|
||||
|
||||
| Trigger | Action |
|
||||
|---------|--------|
|
||||
| Need physics not in library | Follow this protocol |
|
||||
| "create extractor", "new extractor" | Follow this protocol |
|
||||
| Custom result extraction needed | Follow this protocol |
|
||||
|
||||
**First**: Check [SYS_12_EXTRACTOR_LIBRARY](../system/SYS_12_EXTRACTOR_LIBRARY.md) - the functionality may already exist!
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
**Create in**: `optimization_engine/extractors/`
|
||||
**Export from**: `optimization_engine/extractors/__init__.py`
|
||||
**Document in**: Update SYS_12 and this protocol
|
||||
|
||||
**Template location**: `docs/protocols/extensions/templates/extractor_template.py`
|
||||
|
||||
---
|
||||
|
||||
## Step-by-Step Guide
|
||||
|
||||
### Step 1: Verify Need
|
||||
|
||||
Before creating:
|
||||
1. Check existing extractors in [SYS_12](../system/SYS_12_EXTRACTOR_LIBRARY.md)
|
||||
2. Search codebase: `grep -r "your_physics" optimization_engine/`
|
||||
3. Confirm no existing solution
|
||||
|
||||
### Step 1.5: Research NX Open APIs (REQUIRED for NX extractors)
|
||||
|
||||
**If the extractor needs NX Open APIs** (not just pyNastran OP2 parsing):
|
||||
|
||||
```
|
||||
# 1. Search for relevant NX Open APIs
|
||||
siemens_docs_search("inertia properties NXOpen")
|
||||
siemens_docs_search("mass properties body NXOpen.CAE")
|
||||
|
||||
# 2. Fetch detailed documentation for promising classes
|
||||
siemens_docs_fetch("NXOpen.MeasureManager")
|
||||
siemens_docs_fetch("NXOpen.UF.UFWeight")
|
||||
|
||||
# 3. Get method signatures
|
||||
siemens_docs_search("AskMassProperties NXOpen")
|
||||
```
|
||||
|
||||
**When to use NX Open vs pyNastran:**
|
||||
|
||||
| Data Source | Tool | Example |
|
||||
|-------------|------|---------|
|
||||
| OP2 results (stress, disp, freq) | pyNastran | `extract_displacement()` |
|
||||
| CAD properties (mass, inertia) | NX Open | New extractor with NXOpen API |
|
||||
| BDF data (mesh, properties) | pyNastran | `extract_mass_from_bdf()` |
|
||||
| NX expressions | NX Open | `extract_mass_from_expression()` |
|
||||
| FEM model data | NX Open CAE | Needs `NXOpen.CAE.*` APIs |
|
||||
|
||||
**Document the APIs used** in the extractor docstring:
|
||||
```python
|
||||
def extract_inertia(part_file: Path) -> Dict[str, Any]:
|
||||
"""
|
||||
Extract mass and inertia properties from NX part.
|
||||
|
||||
NX Open APIs Used:
|
||||
- NXOpen.MeasureManager.NewMassProperties()
|
||||
- NXOpen.MeasureBodies.InformationUnit
|
||||
- NXOpen.UF.UFWeight.AskProps()
|
||||
|
||||
See: docs.sw.siemens.com for full API reference
|
||||
"""
|
||||
```
|
||||
|
||||
### Step 2: Create Extractor File
|
||||
|
||||
Create `optimization_engine/extractors/extract_{physics}.py`:
|
||||
|
||||
```python
|
||||
"""
|
||||
Extract {Physics Name} from FEA results.
|
||||
|
||||
Author: {Your Name}
|
||||
Created: {Date}
|
||||
Version: 1.0
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, Optional, Union
|
||||
from pyNastran.op2.op2 import OP2
|
||||
|
||||
|
||||
def extract_{physics}(
|
||||
op2_file: Union[str, Path],
|
||||
subcase: int = 1,
|
||||
# Add other parameters as needed
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Extract {physics description} from OP2 file.
|
||||
|
||||
Args:
|
||||
op2_file: Path to the OP2 results file
|
||||
subcase: Subcase number to extract (default: 1)
|
||||
|
||||
Returns:
|
||||
Dictionary containing:
|
||||
- '{main_result}': The primary result value
|
||||
- '{secondary}': Additional result info
|
||||
- 'subcase': The subcase extracted
|
||||
|
||||
Raises:
|
||||
FileNotFoundError: If OP2 file doesn't exist
|
||||
KeyError: If subcase not found in results
|
||||
ValueError: If result data is invalid
|
||||
|
||||
Example:
|
||||
>>> result = extract_{physics}('model.op2', subcase=1)
|
||||
>>> print(result['{main_result}'])
|
||||
123.45
|
||||
"""
|
||||
op2_file = Path(op2_file)
|
||||
|
||||
if not op2_file.exists():
|
||||
raise FileNotFoundError(f"OP2 file not found: {op2_file}")
|
||||
|
||||
# Read OP2 file
|
||||
op2 = OP2()
|
||||
op2.read_op2(str(op2_file))
|
||||
|
||||
# Extract your physics
|
||||
# TODO: Implement extraction logic
|
||||
|
||||
# Example for displacement-like result:
|
||||
if subcase not in op2.displacements:
|
||||
raise KeyError(f"Subcase {subcase} not found in results")
|
||||
|
||||
data = op2.displacements[subcase]
|
||||
# Process data...
|
||||
|
||||
return {
|
||||
'{main_result}': computed_value,
|
||||
'{secondary}': secondary_value,
|
||||
'subcase': subcase,
|
||||
}
|
||||
|
||||
|
||||
# Optional: Class-based extractor for complex cases
|
||||
class {Physics}Extractor:
|
||||
"""
|
||||
Class-based extractor for {physics} with state management.
|
||||
|
||||
Use when extraction requires multiple steps or configuration.
|
||||
"""
|
||||
|
||||
def __init__(self, op2_file: Union[str, Path], **config):
|
||||
self.op2_file = Path(op2_file)
|
||||
self.config = config
|
||||
self._op2 = None
|
||||
|
||||
def _load_op2(self):
|
||||
"""Lazy load OP2 file."""
|
||||
if self._op2 is None:
|
||||
self._op2 = OP2()
|
||||
self._op2.read_op2(str(self.op2_file))
|
||||
return self._op2
|
||||
|
||||
def extract(self, subcase: int = 1) -> Dict[str, Any]:
|
||||
"""Extract results for given subcase."""
|
||||
op2 = self._load_op2()
|
||||
# Implementation here
|
||||
pass
|
||||
```
|
||||
|
||||
### Step 3: Add to __init__.py
|
||||
|
||||
Edit `optimization_engine/extractors/__init__.py`:
|
||||
|
||||
```python
|
||||
# Add import
|
||||
from .extract_{physics} import extract_{physics}
|
||||
# Or for class
|
||||
from .extract_{physics} import {Physics}Extractor
|
||||
|
||||
# Add to __all__
|
||||
__all__ = [
|
||||
# ... existing exports ...
|
||||
'extract_{physics}',
|
||||
'{Physics}Extractor',
|
||||
]
|
||||
```
|
||||
|
||||
### Step 4: Write Tests
|
||||
|
||||
Create `tests/test_extract_{physics}.py`:
|
||||
|
||||
```python
|
||||
"""Tests for {physics} extractor."""
|
||||
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
from optimization_engine.extractors import extract_{physics}
|
||||
|
||||
|
||||
class TestExtract{Physics}:
|
||||
"""Test suite for {physics} extraction."""
|
||||
|
||||
@pytest.fixture
|
||||
def sample_op2(self, tmp_path):
|
||||
"""Create or copy sample OP2 for testing."""
|
||||
# Either copy existing test file or create mock
|
||||
pass
|
||||
|
||||
def test_basic_extraction(self, sample_op2):
|
||||
"""Test basic extraction works."""
|
||||
result = extract_{physics}(sample_op2)
|
||||
assert '{main_result}' in result
|
||||
assert isinstance(result['{main_result}'], float)
|
||||
|
||||
def test_file_not_found(self):
|
||||
"""Test error handling for missing file."""
|
||||
with pytest.raises(FileNotFoundError):
|
||||
extract_{physics}('nonexistent.op2')
|
||||
|
||||
def test_invalid_subcase(self, sample_op2):
|
||||
"""Test error handling for invalid subcase."""
|
||||
with pytest.raises(KeyError):
|
||||
extract_{physics}(sample_op2, subcase=999)
|
||||
```
|
||||
|
||||
### Step 5: Document
|
||||
|
||||
#### Update SYS_12_EXTRACTOR_LIBRARY.md
|
||||
|
||||
Add to Quick Reference table:
|
||||
```markdown
|
||||
| E{N} | {Physics} | `extract_{physics}()` | .op2 | {unit} |
|
||||
```
|
||||
|
||||
Add detailed section:
|
||||
```markdown
|
||||
### E{N}: {Physics} Extraction
|
||||
|
||||
**Module**: `optimization_engine.extractors.extract_{physics}`
|
||||
|
||||
\`\`\`python
|
||||
from optimization_engine.extractors import extract_{physics}
|
||||
|
||||
result = extract_{physics}(op2_file, subcase=1)
|
||||
{main_result} = result['{main_result}']
|
||||
\`\`\`
|
||||
```
|
||||
|
||||
#### Update skills/modules/extractors-catalog.md
|
||||
|
||||
Add entry following existing pattern.
|
||||
|
||||
### Step 6: Validate
|
||||
|
||||
```bash
|
||||
# Run tests
|
||||
pytest tests/test_extract_{physics}.py -v
|
||||
|
||||
# Test import
|
||||
python -c "from optimization_engine.extractors import extract_{physics}; print('OK')"
|
||||
|
||||
# Test with real file
|
||||
python -c "
|
||||
from optimization_engine.extractors import extract_{physics}
|
||||
result = extract_{physics}('path/to/test.op2')
|
||||
print(result)
|
||||
"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Extractor Design Guidelines
|
||||
|
||||
### Do's
|
||||
|
||||
- Return dictionaries with clear keys
|
||||
- Include metadata (subcase, units, etc.)
|
||||
- Handle edge cases gracefully
|
||||
- Provide clear error messages
|
||||
- Document all parameters and returns
|
||||
- Write tests
|
||||
|
||||
### Don'ts
|
||||
|
||||
- Don't re-parse OP2 multiple times in one call
|
||||
- Don't hardcode paths
|
||||
- Don't swallow exceptions silently
|
||||
- Don't return raw pyNastran objects
|
||||
- Don't modify input files
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
| Type | Convention | Example |
|
||||
|------|------------|---------|
|
||||
| File | `extract_{physics}.py` | `extract_thermal.py` |
|
||||
| Function | `extract_{physics}` | `extract_thermal` |
|
||||
| Class | `{Physics}Extractor` | `ThermalExtractor` |
|
||||
| Return key | lowercase_with_underscores | `max_temperature` |
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### Example: Thermal Gradient Extractor
|
||||
|
||||
```python
|
||||
"""Extract thermal gradients from temperature results."""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any
|
||||
from pyNastran.op2.op2 import OP2
|
||||
import numpy as np
|
||||
|
||||
|
||||
def extract_thermal_gradient(
|
||||
op2_file: Path,
|
||||
subcase: int = 1,
|
||||
direction: str = 'magnitude'
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Extract thermal gradient from temperature field.
|
||||
|
||||
Args:
|
||||
op2_file: Path to OP2 file
|
||||
subcase: Subcase number
|
||||
direction: 'magnitude', 'x', 'y', or 'z'
|
||||
|
||||
Returns:
|
||||
Dictionary with gradient results
|
||||
"""
|
||||
op2 = OP2()
|
||||
op2.read_op2(str(op2_file))
|
||||
|
||||
temps = op2.temperatures[subcase]
|
||||
# Calculate gradient...
|
||||
|
||||
return {
|
||||
'max_gradient': max_grad,
|
||||
'mean_gradient': mean_grad,
|
||||
'max_gradient_location': location,
|
||||
'direction': direction,
|
||||
'subcase': subcase,
|
||||
'unit': 'K/mm'
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Cause | Solution |
|
||||
|-------|-------|----------|
|
||||
| Import error | Not added to __init__.py | Add export |
|
||||
| "No module" | Wrong file location | Check path |
|
||||
| KeyError | Wrong OP2 data structure | Debug OP2 contents |
|
||||
| Tests fail | Missing test data | Create fixtures |
|
||||
|
||||
---
|
||||
|
||||
## Cross-References
|
||||
|
||||
- **Reference**: [SYS_12_EXTRACTOR_LIBRARY](../system/SYS_12_EXTRACTOR_LIBRARY.md)
|
||||
- **Template**: `templates/extractor_template.py`
|
||||
- **Related**: [EXT_02_CREATE_HOOK](./EXT_02_CREATE_HOOK.md)
|
||||
|
||||
---
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Date | Changes |
|
||||
|---------|------|---------|
|
||||
| 1.0 | 2025-12-05 | Initial release |
|
||||
Reference in New Issue
Block a user