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>
9.7 KiB
EXT_01: Create New Extractor
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 - 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:
- Check existing extractors in SYS_12
- Search codebase:
grep -r "your_physics" optimization_engine/ - 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:
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:
"""
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:
# 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:
"""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:
| E{N} | {Physics} | `extract_{physics}()` | .op2 | {unit} |
Add detailed section:
### 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
# 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
"""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
- Template:
templates/extractor_template.py - Related: EXT_02_CREATE_HOOK
Version History
| Version | Date | Changes |
|---|---|---|
| 1.0 | 2025-12-05 | Initial release |