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>
396 lines
9.7 KiB
Markdown
396 lines
9.7 KiB
Markdown
# 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 |
|