Phase 3 implements automated OP2 extraction code generation using pyNastran documentation research. This completes the zero-manual-coding pipeline for FEA optimization workflows. Key Features: - PyNastranResearchAgent for automated OP2 code generation - Documentation research via WebFetch integration - 3 core extraction patterns (displacement, stress, force) - Knowledge base architecture for learned patterns - Successfully tested on real OP2 files Phase 2.9 Integration: - Updated HookGenerator with lifecycle hook generation - Added POST_CALCULATION hook point to hooks.py - Created post_calculation/ plugin directory - Generated hooks integrate seamlessly with HookManager New Files: - optimization_engine/pynastran_research_agent.py (600+ lines) - optimization_engine/hook_generator.py (800+ lines) - optimization_engine/inline_code_generator.py - optimization_engine/plugins/post_calculation/ - tests/test_lifecycle_hook_integration.py - docs/SESSION_SUMMARY_PHASE_3.md - docs/SESSION_SUMMARY_PHASE_2_9.md - docs/SESSION_SUMMARY_PHASE_2_8.md - docs/HOOK_ARCHITECTURE.md Modified Files: - README.md - Added Phase 3 completion status - optimization_engine/plugins/hooks.py - Added POST_CALCULATION hook Test Results: - Phase 3 research agent: PASSED - Real OP2 extraction: PASSED (max_disp=0.362mm) - Lifecycle hook integration: PASSED Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
14 KiB
Hook Architecture - Unified Lifecycle System
Overview
Atomizer uses a unified lifecycle hook system where all hooks - whether system plugins or auto-generated post-processing scripts - integrate seamlessly through the HookManager.
Hook Types
1. Lifecycle Hooks (Phase 1 - System Plugins)
Located in: optimization_engine/plugins/<hook_point>/
Purpose: Plugin system for FEA workflow automation
Hook Points:
pre_mesh → Before meshing
post_mesh → After meshing, before solve
pre_solve → Before FEA solver execution
post_solve → After solve, before extraction
post_extraction → After result extraction
post_calculation → After inline calculations (NEW in Phase 2.9)
custom_objective → Custom objective functions
Example: System logging, state management, file operations
2. Generated Post-Processing Hooks (Phase 2.9)
Located in: optimization_engine/plugins/post_calculation/ (by default)
Purpose: Auto-generated custom calculations on extracted data
Can be placed at ANY hook point for maximum flexibility!
Types:
- Weighted objectives
- Custom formulas
- Constraint checks
- Comparisons (ratios, differences, percentages)
Complete Optimization Workflow
Optimization Trial N
↓
┌─────────────────────────────────────┐
│ PRE-SOLVE HOOKS │
│ - Log trial parameters │
│ - Validate design variables │
│ - Backup model files │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ RUN NX NASTRAN SOLVE │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ POST-SOLVE HOOKS │
│ - Check solution convergence │
│ - Log solve completion │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ EXTRACT RESULTS (OP2/F06) │
│ - Read stress, displacement, etc. │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ POST-EXTRACTION HOOKS │
│ - Log extracted values │
│ - Validate result ranges │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ INLINE CALCULATIONS (Phase 2.8) │
│ - avg_stress = sum(stresses) / len │
│ - norm_stress = avg_stress / 200 │
│ - norm_disp = max_disp / 5 │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ POST-CALCULATION HOOKS (Phase 2.9) │
│ - weighted_objective() │
│ - safety_factor() │
│ - constraint_check() │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ REPORT TO OPTUNA │
│ - Return objective value(s) │
└─────────────────────────────────────┘
↓
Next Trial
Directory Structure
optimization_engine/plugins/
├── hooks.py # HookPoint enum, Hook dataclass
├── hook_manager.py # HookManager class
├── pre_mesh/ # Pre-meshing hooks
├── post_mesh/ # Post-meshing hooks
├── pre_solve/ # Pre-solve hooks
│ ├── detailed_logger.py
│ └── optimization_logger.py
├── post_solve/ # Post-solve hooks
│ └── log_solve_complete.py
├── post_extraction/ # Post-extraction hooks
│ ├── log_results.py
│ └── optimization_logger_results.py
└── post_calculation/ # Post-calculation hooks (NEW!)
├── weighted_objective_test.py # Generated by Phase 2.9
├── safety_factor_hook.py # Generated by Phase 2.9
└── min_to_avg_ratio_hook.py # Generated by Phase 2.9
Hook Format
All hooks follow the same interface:
def my_hook(context: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""
Hook function.
Args:
context: Dictionary containing relevant data:
- trial_number: Current optimization trial
- design_variables: Current design variable values
- results: Extracted FEA results (post-extraction)
- calculations: Inline calculation results (post-calculation)
Returns:
Optional dictionary with results to add to context
"""
# Hook logic here
return {'my_result': value}
def register_hooks(hook_manager):
"""Register this hook with the HookManager."""
hook_manager.register_hook(
hook_point='post_calculation', # or any other HookPoint
function=my_hook,
description="My custom hook",
name="my_hook",
priority=100,
enabled=True
)
Hook Generation (Phase 2.9)
Standalone Scripts (Original)
Generated as independent Python scripts with JSON I/O:
from optimization_engine.hook_generator import HookGenerator
generator = HookGenerator()
hook_spec = {
"action": "weighted_objective",
"description": "Combine stress and displacement",
"params": {
"inputs": ["norm_stress", "norm_disp"],
"weights": [0.7, 0.3]
}
}
# Generate standalone script
hook = generator.generate_from_llm_output(hook_spec)
generator.save_hook_to_file(hook, "generated_hooks/")
Use case: Independent execution, debugging, external tools
Lifecycle Hooks (Integrated)
Generated as lifecycle-compatible plugins:
from optimization_engine.hook_generator import HookGenerator
generator = HookGenerator()
hook_spec = {
"action": "weighted_objective",
"description": "Combine stress and displacement",
"params": {
"inputs": ["norm_stress", "norm_disp"],
"weights": [0.7, 0.3]
}
}
# Generate lifecycle hook
hook_content = generator.generate_lifecycle_hook(
hook_spec,
hook_point='post_calculation' # or pre_solve, post_extraction, etc.
)
# Save to plugins directory
output_file = Path("optimization_engine/plugins/post_calculation/weighted_objective.py")
with open(output_file, 'w') as f:
f.write(hook_content)
# HookManager automatically discovers and loads it!
Use case: Integration with optimization workflow, automatic execution
Flexibility: Hooks Can Be Placed Anywhere!
The beauty of the lifecycle system is that generated hooks can be placed at ANY hook point:
Example 1: Pre-Solve Validation
# Generate a constraint check to run BEFORE solving
constraint_spec = {
"action": "constraint_check",
"description": "Ensure wall thickness is reasonable",
"params": {
"inputs": ["wall_thickness", "max_thickness"],
"condition": "wall_thickness / max_thickness",
"threshold": 1.0,
"constraint_name": "thickness_check"
}
}
hook_content = generator.generate_lifecycle_hook(
constraint_spec,
hook_point='pre_solve' # Run BEFORE solve!
)
###Example 2: Post-Extraction Safety Factor
# Generate safety factor calculation right after extraction
safety_spec = {
"action": "custom_formula",
"description": "Calculate safety factor from extracted stress",
"params": {
"inputs": ["max_stress", "yield_strength"],
"formula": "yield_strength / max_stress",
"output_name": "safety_factor"
}
}
hook_content = generator.generate_lifecycle_hook(
safety_spec,
hook_point='post_extraction' # Run right after extraction!
)
Example 3: Pre-Mesh Parameter Validation
# Generate parameter check before meshing
validation_spec = {
"action": "comparison",
"description": "Check if thickness exceeds maximum",
"params": {
"inputs": ["requested_thickness", "max_allowed"],
"operation": "ratio",
"output_name": "thickness_ratio"
}
}
hook_content = generator.generate_lifecycle_hook(
validation_spec,
hook_point='pre_mesh' # Run before meshing!
)
Hook Manager Usage
from optimization_engine.plugins.hook_manager import HookManager
# Create manager
hook_manager = HookManager()
# Auto-load all plugins from directory structure
hook_manager.load_plugins_from_directory(
Path("optimization_engine/plugins")
)
# Execute hooks at specific point
context = {
'trial_number': 42,
'results': {'max_stress': 150.5},
'calculations': {'norm_stress': 0.75, 'norm_disp': 0.64}
}
results = hook_manager.execute_hooks('post_calculation', context)
# Get summary
summary = hook_manager.get_summary()
print(f"Total hooks: {summary['total_hooks']}")
print(f"Hooks at post_calculation: {summary['by_hook_point']['post_calculation']}")
Integration with Optimization Runner
The optimization runner will be updated to call hooks at appropriate lifecycle points:
# In optimization_engine/runner.py
def run_trial(self, trial_number, design_variables):
# Create context
context = {
'trial_number': trial_number,
'design_variables': design_variables,
'working_dir': self.working_dir
}
# Pre-solve hooks
self.hook_manager.execute_hooks('pre_solve', context)
# Run solve
self.nx_solver.run(...)
# Post-solve hooks
self.hook_manager.execute_hooks('post_solve', context)
# Extract results
results = self.extractor.extract(...)
context['results'] = results
# Post-extraction hooks
self.hook_manager.execute_hooks('post_extraction', context)
# Inline calculations (Phase 2.8)
calculations = self.inline_calculator.calculate(...)
context['calculations'] = calculations
# Post-calculation hooks (Phase 2.9)
hook_results = self.hook_manager.execute_hooks('post_calculation', context)
# Merge hook results into context
for result in hook_results:
if result:
context.update(result)
# Return final objective
return context.get('weighted_objective') or results['stress']
Benefits of Unified System
- Consistency: All hooks use same interface, same registration, same execution
- Flexibility: Generated hooks can be placed at any lifecycle point
- Discoverability: HookManager auto-loads from directory structure
- Extensibility: Easy to add new hook points or new hook types
- Debugging: All hooks have logging, history tracking, enable/disable
- Priority Control: Hooks execute in priority order
- Error Handling: Configurable fail-fast or continue-on-error
Example: Complete CBAR Optimization
User Request:
"Extract CBAR element forces in Z direction, calculate average and minimum, create objective that minimizes min/avg ratio, optimize CBAR stiffness X with genetic algorithm"
Phase 2.7 LLM Analysis:
{
"engineering_features": [
{"action": "extract_1d_element_forces", "domain": "result_extraction"},
{"action": "update_cbar_stiffness", "domain": "fea_properties"}
],
"inline_calculations": [
{"action": "calculate_average", "params": {"input": "forces_z"}},
{"action": "find_minimum", "params": {"input": "forces_z"}}
],
"post_processing_hooks": [
{
"action": "comparison",
"params": {
"inputs": ["min_force", "avg_force"],
"operation": "ratio",
"output_name": "min_to_avg_ratio"
}
}
]
}
Phase 2.8 Generated (Inline):
avg_forces_z = sum(forces_z) / len(forces_z)
min_forces_z = min(forces_z)
Phase 2.9 Generated (Lifecycle Hook):
# optimization_engine/plugins/post_calculation/min_to_avg_ratio_hook.py
def min_to_avg_ratio_hook(context):
calculations = context.get('calculations', {})
min_force = calculations.get('min_forces_z')
avg_force = calculations.get('avg_forces_z')
result = min_force / avg_force
return {'min_to_avg_ratio': result, 'objective': result}
def register_hooks(hook_manager):
hook_manager.register_hook(
hook_point='post_calculation',
function=min_to_avg_ratio_hook,
description="Compare min force to average",
name="min_to_avg_ratio_hook"
)
Execution:
Trial 1:
pre_solve hooks → log trial
solve → NX Nastran
post_solve hooks → check convergence
post_extraction hooks → validate results
Extract: forces_z = [10.5, 12.3, 8.9, 11.2, 9.8]
Inline calculations:
avg_forces_z = 10.54
min_forces_z = 8.9
post_calculation hooks → min_to_avg_ratio_hook
min_to_avg_ratio = 8.9 / 10.54 = 0.844
Report to Optuna: objective = 0.844
All code auto-generated! Zero manual scripting! 🚀
Future Enhancements
- Hook Dependencies: Hooks can declare dependencies on other hooks
- Conditional Execution: Hooks can have conditions (e.g., only run if stress > threshold)
- Hook Composition: Combine multiple hooks into pipelines
- Study-Specific Hooks: Hooks stored in
studies/<study_name>/plugins/ - Hook Marketplace: Share hooks between projects/users
Summary
The unified lifecycle hook system provides:
- ✅ Single consistent interface for all hooks
- ✅ Generated hooks integrate seamlessly with system hooks
- ✅ Hooks can be placed at ANY lifecycle point
- ✅ Auto-discovery and loading
- ✅ Priority control and error handling
- ✅ Maximum flexibility for optimization workflows
Phase 2.9 hooks are now true lifecycle hooks, usable anywhere in the FEA workflow!