refactor: Major reorganization of optimization_engine module structure
BREAKING CHANGE: Module paths have been reorganized for better maintainability. Backwards compatibility aliases with deprecation warnings are provided. New Structure: - core/ - Optimization runners (runner, intelligent_optimizer, etc.) - processors/ - Data processing - surrogates/ - Neural network surrogates - nx/ - NX/Nastran integration (solver, updater, session_manager) - study/ - Study management (creator, wizard, state, reset) - reporting/ - Reports and analysis (visualizer, report_generator) - config/ - Configuration management (manager, builder) - utils/ - Utilities (logger, auto_doc, etc.) - future/ - Research/experimental code Migration: - ~200 import changes across 125 files - All __init__.py files use lazy loading to avoid circular imports - Backwards compatibility layer supports old import paths with warnings - All existing functionality preserved To migrate existing code: OLD: from optimization_engine.nx_solver import NXSolver NEW: from optimization_engine.nx.solver import NXSolver OLD: from optimization_engine.runner import OptimizationRunner NEW: from optimization_engine.core.runner import OptimizationRunner 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
402
optimization_engine/future/pynastran_research_agent.py
Normal file
402
optimization_engine/future/pynastran_research_agent.py
Normal file
@@ -0,0 +1,402 @@
|
||||
"""
|
||||
pyNastran Research Agent - Phase 3
|
||||
|
||||
Automated research and code generation for OP2 result extraction using pyNastran.
|
||||
|
||||
This agent:
|
||||
1. Searches pyNastran documentation
|
||||
2. Finds relevant APIs for extraction tasks
|
||||
3. Generates executable Python code for extractors
|
||||
4. Stores patterns in knowledge base
|
||||
|
||||
Author: Atomizer Development Team
|
||||
Version: 0.1.0 (Phase 3)
|
||||
Last Updated: 2025-01-16
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, List, Optional
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
import json
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExtractionPattern:
|
||||
"""Represents a learned pattern for OP2 extraction."""
|
||||
name: str
|
||||
description: str
|
||||
element_type: Optional[str] # e.g., 'CBAR', 'CQUAD4', None for general
|
||||
result_type: str # 'force', 'stress', 'displacement', 'strain'
|
||||
code_template: str
|
||||
api_path: str # e.g., 'model.cbar_force[subcase]'
|
||||
data_structure: str # Description of data array structure
|
||||
examples: List[str] # Example usage
|
||||
|
||||
|
||||
class PyNastranResearchAgent:
|
||||
"""
|
||||
Research agent for pyNastran documentation and code generation.
|
||||
|
||||
Uses a combination of:
|
||||
- Pre-learned patterns from documentation
|
||||
- WebFetch for dynamic lookup (future)
|
||||
- Knowledge base caching
|
||||
"""
|
||||
|
||||
def __init__(self, knowledge_base_path: Optional[Path] = None):
|
||||
"""
|
||||
Initialize the research agent.
|
||||
|
||||
Args:
|
||||
knowledge_base_path: Path to store learned patterns
|
||||
"""
|
||||
if knowledge_base_path is None:
|
||||
knowledge_base_path = Path(__file__).parent.parent / "knowledge_base" / "pynastran_patterns"
|
||||
|
||||
self.knowledge_base_path = Path(knowledge_base_path)
|
||||
self.knowledge_base_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Initialize with core patterns from documentation research
|
||||
self.patterns = self._initialize_core_patterns()
|
||||
|
||||
def _initialize_core_patterns(self) -> Dict[str, ExtractionPattern]:
|
||||
"""Initialize core extraction patterns from pyNastran docs."""
|
||||
patterns = {}
|
||||
|
||||
# Displacement extraction
|
||||
patterns['displacement'] = ExtractionPattern(
|
||||
name='displacement',
|
||||
description='Extract displacement results',
|
||||
element_type=None,
|
||||
result_type='displacement',
|
||||
code_template='''def extract_displacement(op2_file: Path, subcase: int = 1):
|
||||
"""Extract displacement results from OP2 file."""
|
||||
from pyNastran.op2.op2 import OP2
|
||||
import numpy as np
|
||||
|
||||
model = OP2()
|
||||
model.read_op2(str(op2_file))
|
||||
|
||||
disp = model.displacements[subcase]
|
||||
itime = 0 # static case
|
||||
|
||||
# Extract translation components
|
||||
txyz = disp.data[itime, :, :3] # [tx, ty, tz]
|
||||
|
||||
# Calculate total displacement
|
||||
total_disp = np.linalg.norm(txyz, axis=1)
|
||||
max_disp = np.max(total_disp)
|
||||
|
||||
# Get node info
|
||||
node_ids = [nid for (nid, grid_type) in disp.node_gridtype]
|
||||
max_disp_node = node_ids[np.argmax(total_disp)]
|
||||
|
||||
return {
|
||||
'max_displacement': float(max_disp),
|
||||
'max_disp_node': int(max_disp_node),
|
||||
'max_disp_x': float(np.max(np.abs(txyz[:, 0]))),
|
||||
'max_disp_y': float(np.max(np.abs(txyz[:, 1]))),
|
||||
'max_disp_z': float(np.max(np.abs(txyz[:, 2])))
|
||||
}''',
|
||||
api_path='model.displacements[subcase]',
|
||||
data_structure='data[itime, :, :6] where :6=[tx, ty, tz, rx, ry, rz]',
|
||||
examples=['max_disp = extract_displacement(Path("results.op2"))']
|
||||
)
|
||||
|
||||
# Stress extraction (solid elements)
|
||||
patterns['solid_stress'] = ExtractionPattern(
|
||||
name='solid_stress',
|
||||
description='Extract stress from solid elements (CTETRA, CHEXA)',
|
||||
element_type='CTETRA',
|
||||
result_type='stress',
|
||||
code_template='''def extract_solid_stress(op2_file: Path, subcase: int = 1, element_type: str = 'ctetra'):
|
||||
"""Extract stress from solid elements."""
|
||||
from pyNastran.op2.op2 import OP2
|
||||
import numpy as np
|
||||
|
||||
model = OP2()
|
||||
model.read_op2(str(op2_file))
|
||||
|
||||
# Get stress object for element type
|
||||
# In pyNastran, stress is stored in model.op2_results.stress
|
||||
stress_attr = f"{element_type}_stress"
|
||||
|
||||
if not hasattr(model, 'op2_results') or not hasattr(model.op2_results, 'stress'):
|
||||
raise ValueError(f"No stress results in OP2")
|
||||
|
||||
stress_obj = model.op2_results.stress
|
||||
if not hasattr(stress_obj, stress_attr):
|
||||
raise ValueError(f"No {element_type} stress results in OP2")
|
||||
|
||||
stress = getattr(stress_obj, stress_attr)[subcase]
|
||||
itime = 0
|
||||
|
||||
# Extract von Mises if available
|
||||
if stress.is_von_mises: # Property, not method
|
||||
von_mises = stress.data[itime, :, 9] # Column 9 is von Mises
|
||||
max_stress = float(np.max(von_mises))
|
||||
|
||||
# Get element info
|
||||
element_ids = [eid for (eid, node) in stress.element_node]
|
||||
max_stress_elem = element_ids[np.argmax(von_mises)]
|
||||
|
||||
return {
|
||||
'max_von_mises': max_stress,
|
||||
'max_stress_element': int(max_stress_elem)
|
||||
}
|
||||
else:
|
||||
raise ValueError("von Mises stress not available")''',
|
||||
api_path='model.ctetra_stress[subcase] or model.chexa_stress[subcase]',
|
||||
data_structure='data[itime, :, 10] where column 9=von_mises',
|
||||
examples=['stress = extract_solid_stress(Path("results.op2"), element_type="ctetra")']
|
||||
)
|
||||
|
||||
# CBAR force extraction
|
||||
patterns['cbar_force'] = ExtractionPattern(
|
||||
name='cbar_force',
|
||||
description='Extract forces from CBAR elements',
|
||||
element_type='CBAR',
|
||||
result_type='force',
|
||||
code_template='''def extract_cbar_force(op2_file: Path, subcase: int = 1, direction: str = 'Z'):
|
||||
"""
|
||||
Extract forces from CBAR elements.
|
||||
|
||||
Args:
|
||||
op2_file: Path to OP2 file
|
||||
subcase: Subcase ID
|
||||
direction: Force direction ('X', 'Y', 'Z', 'axial', 'torque')
|
||||
|
||||
Returns:
|
||||
Dict with force statistics
|
||||
"""
|
||||
from pyNastran.op2.op2 import OP2
|
||||
import numpy as np
|
||||
|
||||
model = OP2()
|
||||
model.read_op2(str(op2_file))
|
||||
|
||||
if not hasattr(model, 'cbar_force'):
|
||||
raise ValueError("No CBAR force results in OP2")
|
||||
|
||||
force = model.cbar_force[subcase]
|
||||
itime = 0
|
||||
|
||||
# CBAR force data structure:
|
||||
# [bending_moment_a1, bending_moment_a2,
|
||||
# bending_moment_b1, bending_moment_b2,
|
||||
# shear1, shear2, axial, torque]
|
||||
|
||||
direction_map = {
|
||||
'shear1': 4,
|
||||
'shear2': 5,
|
||||
'axial': 6,
|
||||
'Z': 6, # Commonly axial is Z direction
|
||||
'torque': 7
|
||||
}
|
||||
|
||||
col_idx = direction_map.get(direction, direction_map.get(direction.lower(), 6))
|
||||
forces = force.data[itime, :, col_idx]
|
||||
|
||||
return {
|
||||
f'max_{direction}_force': float(np.max(np.abs(forces))),
|
||||
f'avg_{direction}_force': float(np.mean(np.abs(forces))),
|
||||
f'min_{direction}_force': float(np.min(np.abs(forces))),
|
||||
'forces_array': forces.tolist()
|
||||
}''',
|
||||
api_path='model.cbar_force[subcase]',
|
||||
data_structure='data[ntimes, nelements, 8] where 8=[bm_a1, bm_a2, bm_b1, bm_b2, shear1, shear2, axial, torque]',
|
||||
examples=['forces = extract_cbar_force(Path("results.op2"), direction="Z")']
|
||||
)
|
||||
|
||||
return patterns
|
||||
|
||||
def research_extraction(self, request: Dict[str, Any]) -> ExtractionPattern:
|
||||
"""
|
||||
Research and find/generate extraction pattern for a request.
|
||||
|
||||
Args:
|
||||
request: Dict with:
|
||||
- action: e.g., 'extract_1d_element_forces'
|
||||
- domain: e.g., 'result_extraction'
|
||||
- params: {'element_types': ['CBAR'], 'result_type': 'element_force', 'direction': 'Z'}
|
||||
|
||||
Returns:
|
||||
ExtractionPattern with code template
|
||||
"""
|
||||
action = request.get('action', '')
|
||||
params = request.get('params', {})
|
||||
|
||||
# Determine result type
|
||||
if 'displacement' in action.lower():
|
||||
return self.patterns['displacement']
|
||||
|
||||
elif 'stress' in action.lower():
|
||||
element_types = params.get('element_types', [])
|
||||
if any(et in ['CTETRA', 'CHEXA', 'CPENTA'] for et in element_types):
|
||||
return self.patterns['solid_stress']
|
||||
# Could add plate stress pattern here
|
||||
return self.patterns['solid_stress'] # Default to solid for now
|
||||
|
||||
elif 'force' in action.lower() or 'element_force' in params.get('result_type', ''):
|
||||
element_types = params.get('element_types', [])
|
||||
if 'CBAR' in element_types or '1d' in action.lower():
|
||||
return self.patterns['cbar_force']
|
||||
|
||||
# Fallback: return generic pattern
|
||||
return self._generate_generic_pattern(request)
|
||||
|
||||
def _generate_generic_pattern(self, request: Dict[str, Any]) -> ExtractionPattern:
|
||||
"""Generate a generic extraction pattern as fallback."""
|
||||
return ExtractionPattern(
|
||||
name='generic_extraction',
|
||||
description=f"Generic extraction for {request.get('action', 'unknown')}",
|
||||
element_type=None,
|
||||
result_type='unknown',
|
||||
code_template='''def extract_generic(op2_file: Path):
|
||||
"""Generic OP2 extraction - needs customization."""
|
||||
from pyNastran.op2.op2 import OP2
|
||||
|
||||
model = OP2()
|
||||
model.read_op2(str(op2_file))
|
||||
|
||||
# TODO: Customize extraction based on requirements
|
||||
# Available: model.displacements, model.ctetra_stress, etc.
|
||||
# Use model.get_op2_stats() to see available results
|
||||
|
||||
return {'result': None}''',
|
||||
api_path='model.<result_type>[subcase]',
|
||||
data_structure='Varies by result type',
|
||||
examples=['# Needs customization']
|
||||
)
|
||||
|
||||
def generate_extractor_code(self, request: Dict[str, Any]) -> str:
|
||||
"""
|
||||
Generate complete extractor code for a request.
|
||||
|
||||
Args:
|
||||
request: Extraction request from Phase 2.7 LLM
|
||||
|
||||
Returns:
|
||||
Complete Python code as string
|
||||
"""
|
||||
pattern = self.research_extraction(request)
|
||||
|
||||
# Generate module header
|
||||
description = request.get('description', pattern.description)
|
||||
|
||||
code = f'''"""
|
||||
{description}
|
||||
Auto-generated by Atomizer Phase 3 - pyNastran Research Agent
|
||||
|
||||
Pattern: {pattern.name}
|
||||
Element Type: {pattern.element_type or 'General'}
|
||||
Result Type: {pattern.result_type}
|
||||
API: {pattern.api_path}
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any
|
||||
import numpy as np
|
||||
from pyNastran.op2.op2 import OP2
|
||||
|
||||
|
||||
{pattern.code_template}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Example usage
|
||||
import sys
|
||||
if len(sys.argv) > 1:
|
||||
op2_file = Path(sys.argv[1])
|
||||
result = {pattern.code_template.split('(')[0].split()[-1]}(op2_file)
|
||||
print(f"Extraction result: {{result}}")
|
||||
else:
|
||||
print("Usage: python {{sys.argv[0]}} <op2_file>")
|
||||
'''
|
||||
|
||||
return code
|
||||
|
||||
def save_pattern(self, pattern: ExtractionPattern):
|
||||
"""Save a pattern to the knowledge base."""
|
||||
pattern_file = self.knowledge_base_path / f"{pattern.name}.json"
|
||||
|
||||
pattern_dict = {
|
||||
'name': pattern.name,
|
||||
'description': pattern.description,
|
||||
'element_type': pattern.element_type,
|
||||
'result_type': pattern.result_type,
|
||||
'code_template': pattern.code_template,
|
||||
'api_path': pattern.api_path,
|
||||
'data_structure': pattern.data_structure,
|
||||
'examples': pattern.examples
|
||||
}
|
||||
|
||||
with open(pattern_file, 'w') as f:
|
||||
json.dump(pattern_dict, f, indent=2)
|
||||
|
||||
def load_pattern(self, name: str) -> Optional[ExtractionPattern]:
|
||||
"""Load a pattern from the knowledge base."""
|
||||
pattern_file = self.knowledge_base_path / f"{name}.json"
|
||||
|
||||
if not pattern_file.exists():
|
||||
return None
|
||||
|
||||
with open(pattern_file, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
return ExtractionPattern(**data)
|
||||
|
||||
|
||||
def main():
|
||||
"""Test the pyNastran research agent."""
|
||||
print("=" * 80)
|
||||
print("Phase 3: pyNastran Research Agent Test")
|
||||
print("=" * 80)
|
||||
print()
|
||||
|
||||
agent = PyNastranResearchAgent()
|
||||
|
||||
# Test request: CBAR force extraction (from Phase 2.7 example)
|
||||
test_request = {
|
||||
"action": "extract_1d_element_forces",
|
||||
"domain": "result_extraction",
|
||||
"description": "Extract element forces from CBAR in Z direction from OP2",
|
||||
"params": {
|
||||
"element_types": ["CBAR"],
|
||||
"result_type": "element_force",
|
||||
"direction": "Z"
|
||||
}
|
||||
}
|
||||
|
||||
print("Test Request:")
|
||||
print(f" Action: {test_request['action']}")
|
||||
print(f" Description: {test_request['description']}")
|
||||
print()
|
||||
|
||||
print("1. Researching extraction pattern...")
|
||||
pattern = agent.research_extraction(test_request)
|
||||
print(f" Found pattern: {pattern.name}")
|
||||
print(f" API path: {pattern.api_path}")
|
||||
print()
|
||||
|
||||
print("2. Generating extractor code...")
|
||||
code = agent.generate_extractor_code(test_request)
|
||||
print()
|
||||
|
||||
print("=" * 80)
|
||||
print("Generated Extractor Code:")
|
||||
print("=" * 80)
|
||||
print(code)
|
||||
|
||||
# Save to file
|
||||
output_file = Path("generated_extractors") / "cbar_force_extractor.py"
|
||||
output_file.parent.mkdir(exist_ok=True)
|
||||
with open(output_file, 'w') as f:
|
||||
f.write(code)
|
||||
|
||||
print()
|
||||
print(f"[OK] Saved to: {output_file}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user