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:
186
docs/protocols/extensions/templates/extractor_template.py
Normal file
186
docs/protocols/extensions/templates/extractor_template.py
Normal file
@@ -0,0 +1,186 @@
|
||||
"""
|
||||
Extract {Physics Name} from FEA results.
|
||||
|
||||
This is a template for creating new physics extractors.
|
||||
Copy this file to optimization_engine/extractors/extract_{physics}.py
|
||||
and customize for your specific physics extraction.
|
||||
|
||||
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 specific to your physics
|
||||
) -> 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)
|
||||
# Document other parameters
|
||||
|
||||
Returns:
|
||||
Dictionary containing:
|
||||
- '{main_result}': The primary result value ({unit})
|
||||
- '{secondary_result}': Secondary result info
|
||||
- 'subcase': The subcase extracted
|
||||
- 'unit': Unit of the result
|
||||
|
||||
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
|
||||
>>> print(result['unit'])
|
||||
'{unit}'
|
||||
"""
|
||||
# Convert to Path for consistency
|
||||
op2_file = Path(op2_file)
|
||||
|
||||
# Validate file exists
|
||||
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))
|
||||
|
||||
# =========================================
|
||||
# CUSTOMIZE: Your extraction logic here
|
||||
# =========================================
|
||||
|
||||
# Example: Access displacement data
|
||||
# if subcase not in op2.displacements:
|
||||
# raise KeyError(f"Subcase {subcase} not found in displacement results")
|
||||
# data = op2.displacements[subcase]
|
||||
|
||||
# Example: Access stress data
|
||||
# if subcase not in op2.cquad4_stress:
|
||||
# raise KeyError(f"Subcase {subcase} not found in stress results")
|
||||
# stress_data = op2.cquad4_stress[subcase]
|
||||
|
||||
# Example: Process data
|
||||
# values = data.data # numpy array
|
||||
# max_value = values.max()
|
||||
# max_index = values.argmax()
|
||||
|
||||
# =========================================
|
||||
# Replace with your actual computation
|
||||
# =========================================
|
||||
main_result = 0.0 # TODO: Compute actual value
|
||||
secondary_result = 0 # TODO: Compute actual value
|
||||
|
||||
return {
|
||||
'{main_result}': main_result,
|
||||
'{secondary_result}': secondary_result,
|
||||
'subcase': subcase,
|
||||
'unit': '{unit}',
|
||||
}
|
||||
|
||||
|
||||
# Optional: Class-based extractor for complex cases
|
||||
class {Physics}Extractor:
|
||||
"""
|
||||
Class-based extractor for {physics} with state management.
|
||||
|
||||
Use this pattern when:
|
||||
- Extraction requires multiple steps
|
||||
- You need to cache the OP2 data
|
||||
- Configuration is complex
|
||||
|
||||
Example:
|
||||
>>> extractor = {Physics}Extractor('model.op2', config={'option': value})
|
||||
>>> result = extractor.extract(subcase=1)
|
||||
>>> print(result)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
op2_file: Union[str, Path],
|
||||
bdf_file: Optional[Union[str, Path]] = None,
|
||||
**config
|
||||
):
|
||||
"""
|
||||
Initialize the extractor.
|
||||
|
||||
Args:
|
||||
op2_file: Path to OP2 results file
|
||||
bdf_file: Optional path to BDF mesh file (for node coordinates)
|
||||
**config: Additional configuration options
|
||||
"""
|
||||
self.op2_file = Path(op2_file)
|
||||
self.bdf_file = Path(bdf_file) if bdf_file else None
|
||||
self.config = config
|
||||
self._op2 = None # Lazy-loaded
|
||||
|
||||
def _load_op2(self) -> OP2:
|
||||
"""Lazy load OP2 file (caches result)."""
|
||||
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.
|
||||
|
||||
Args:
|
||||
subcase: Subcase number
|
||||
|
||||
Returns:
|
||||
Dictionary with extraction results
|
||||
"""
|
||||
op2 = self._load_op2()
|
||||
|
||||
# TODO: Implement your extraction logic
|
||||
# Use self.config for configuration options
|
||||
|
||||
return {
|
||||
'{main_result}': 0.0,
|
||||
'subcase': subcase,
|
||||
}
|
||||
|
||||
def extract_all_subcases(self) -> Dict[int, Dict[str, Any]]:
|
||||
"""
|
||||
Extract results for all available subcases.
|
||||
|
||||
Returns:
|
||||
Dictionary mapping subcase number to results
|
||||
"""
|
||||
op2 = self._load_op2()
|
||||
|
||||
# TODO: Find available subcases
|
||||
# available_subcases = list(op2.displacements.keys())
|
||||
|
||||
results = {}
|
||||
# for sc in available_subcases:
|
||||
# results[sc] = self.extract(subcase=sc)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
# =========================================
|
||||
# After creating your extractor:
|
||||
# 1. Add to optimization_engine/extractors/__init__.py:
|
||||
# from .extract_{physics} import extract_{physics}
|
||||
# __all__ = [..., 'extract_{physics}']
|
||||
#
|
||||
# 2. Update docs/protocols/system/SYS_12_EXTRACTOR_LIBRARY.md
|
||||
# - Add to Quick Reference table
|
||||
# - Add detailed section with example
|
||||
#
|
||||
# 3. Create test file: tests/test_extract_{physics}.py
|
||||
# =========================================
|
||||
Reference in New Issue
Block a user