Files
Atomizer/docs/protocols/extensions/EXT_01_CREATE_EXTRACTOR.md
Antoine 602560c46a 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>
2025-12-06 20:01:59 -05:00

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:

  1. Check existing extractors in SYS_12
  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:

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


Version History

Version Date Changes
1.0 2025-12-05 Initial release