feat: Complete Phase 3.1 - Extractor Orchestration & End-to-End Automation

Phase 3.1 completes the ZERO-MANUAL-CODING automation pipeline by
integrating all phases into a seamless workflow from natural language
request to final objective value.

Key Features:
- ExtractorOrchestrator integrates Phase 2.7 LLM + Phase 3.0 Research Agent
- Automatic extractor generation from LLM workflow output
- Dynamic loading and execution on real OP2 files
- Smart parameter filtering per extraction pattern type
- Multi-extractor support in single workflow
- Complete end-to-end test passed on real bracket OP2

Complete Automation Pipeline:
  User Natural Language Request
      ↓
  Phase 2.7 LLM Analysis
      ↓
  Phase 3.1 Orchestrator
      ↓
  Phase 3.0 Research Agent (auto OP2 code gen)
      ↓
  Generated Extractor Modules
      ↓
  Dynamic Execution on Real OP2
      ↓
  Phase 2.8 Inline Calculations
      ↓
  Phase 2.9 Post-Processing Hooks
      ↓
  Final Objective → Optuna

Test Results:
- Generated displacement extractor: PASSED
- Executed on bracket OP2: PASSED
- Extracted max_displacement: 0.361783mm at node 91
- Calculated normalized objective: 0.072357
- Multi-extractor generation: PASSED

New Files:
- optimization_engine/extractor_orchestrator.py (380+ lines)
- tests/test_phase_3_1_integration.py (200+ lines)
- docs/SESSION_SUMMARY_PHASE_3_1.md (comprehensive documentation)
- optimization_engine/result_extractors/generated/ (auto-generated extractors)

Modified Files:
- README.md - Added Phase 3.1 completion status

ZERO MANUAL CODING - Complete automation achieved!

Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-16 19:39:04 -05:00
parent 38abb0d8d2
commit 90a9e020d8
7 changed files with 1433 additions and 1 deletions

View File

@@ -0,0 +1,73 @@
"""
Extract element forces from CBAR in Z direction from OP2
Auto-generated by Atomizer Phase 3 - pyNastran Research Agent
Pattern: cbar_force
Element Type: CBAR
Result Type: force
API: model.cbar_force[subcase]
"""
from pathlib import Path
from typing import Dict, Any
import numpy as np
from pyNastran.op2.op2 import OP2
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()
}
if __name__ == '__main__':
# Example usage
import sys
if len(sys.argv) > 1:
op2_file = Path(sys.argv[1])
result = extract_cbar_force(op2_file)
print(f"Extraction result: {result}")
else:
print("Usage: python {sys.argv[0]} <op2_file>")

View File

@@ -0,0 +1,56 @@
"""
Extract displacement from OP2
Auto-generated by Atomizer Phase 3 - pyNastran Research Agent
Pattern: displacement
Element Type: General
Result Type: displacement
API: model.displacements[subcase]
"""
from pathlib import Path
from typing import Dict, Any
import numpy as np
from pyNastran.op2.op2 import OP2
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])))
}
if __name__ == '__main__':
# Example usage
import sys
if len(sys.argv) > 1:
op2_file = Path(sys.argv[1])
result = extract_displacement(op2_file)
print(f"Extraction result: {result}")
else:
print("Usage: python {sys.argv[0]} <op2_file>")

View File

@@ -0,0 +1,58 @@
"""
Extract von Mises stress from solid elements
Auto-generated by Atomizer Phase 3 - pyNastran Research Agent
Pattern: solid_stress
Element Type: CTETRA
Result Type: stress
API: model.ctetra_stress[subcase] or model.chexa_stress[subcase]
"""
from pathlib import Path
from typing import Dict, Any
import numpy as np
from pyNastran.op2.op2 import OP2
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
stress_attr = f"{element_type}_stress"
if not hasattr(model, stress_attr):
raise ValueError(f"No {element_type} stress results in OP2")
stress = getattr(model, stress_attr)[subcase]
itime = 0
# Extract von Mises if available
if stress.is_von_mises():
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")
if __name__ == '__main__':
# Example usage
import sys
if len(sys.argv) > 1:
op2_file = Path(sys.argv[1])
result = extract_solid_stress(op2_file)
print(f"Extraction result: {result}")
else:
print("Usage: python {sys.argv[0]} <op2_file>")