feat: Implement Phase 1 - Plugin & Hook System
Core plugin architecture for LLM-driven optimization: New Features: - Hook system with 6 lifecycle points (pre_mesh, post_mesh, pre_solve, post_solve, post_extraction, custom_objectives) - HookManager for centralized registration and execution - Code validation with AST-based safety checks - Feature registry (JSON) for LLM capability discovery - Example plugin: log_trial_start - 23 comprehensive tests (all passing) Integration: - OptimizationRunner now loads plugins automatically - Hooks execute at 5 points in optimization loop - Custom objectives can override total_objective via hooks Safety: - Module whitelist (numpy, scipy, pandas, optuna, pyNastran) - Dangerous operation blocking (eval, exec, os.system, subprocess) - Optional file operation permission flag Files Added: - optimization_engine/plugins/__init__.py - optimization_engine/plugins/hooks.py - optimization_engine/plugins/hook_manager.py - optimization_engine/plugins/validators.py - optimization_engine/feature_registry.json - optimization_engine/plugins/pre_solve/log_trial_start.py - tests/test_plugin_system.py (23 tests) Files Modified: - optimization_engine/runner.py (added hook integration) Ready for Phase 2: LLM interface layer 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,8 @@ import pandas as pd
|
||||
from datetime import datetime
|
||||
import pickle
|
||||
|
||||
from optimization_engine.plugins import HookManager
|
||||
|
||||
|
||||
class OptimizationRunner:
|
||||
"""
|
||||
@@ -68,6 +70,15 @@ class OptimizationRunner:
|
||||
self.output_dir = self.config_path.parent / 'optimization_results'
|
||||
self.output_dir.mkdir(exist_ok=True)
|
||||
|
||||
# Initialize plugin/hook system
|
||||
self.hook_manager = HookManager()
|
||||
plugins_dir = Path(__file__).parent / 'plugins'
|
||||
if plugins_dir.exists():
|
||||
self.hook_manager.load_plugins_from_directory(plugins_dir)
|
||||
summary = self.hook_manager.get_summary()
|
||||
if summary['total_hooks'] > 0:
|
||||
print(f"Loaded {summary['enabled_hooks']}/{summary['total_hooks']} plugins")
|
||||
|
||||
def _load_config(self) -> Dict[str, Any]:
|
||||
"""Load and validate optimization configuration."""
|
||||
with open(self.config_path, 'r') as f:
|
||||
@@ -311,6 +322,16 @@ class OptimizationRunner:
|
||||
int(dv['bounds'][1])
|
||||
)
|
||||
|
||||
# Execute pre_solve hooks
|
||||
pre_solve_context = {
|
||||
'trial_number': trial.number,
|
||||
'design_variables': design_vars,
|
||||
'sim_file': self.config.get('sim_file', ''),
|
||||
'working_dir': str(Path.cwd()),
|
||||
'config': self.config
|
||||
}
|
||||
self.hook_manager.execute_hooks('pre_solve', pre_solve_context, fail_fast=False)
|
||||
|
||||
# 2. Update NX model with new parameters
|
||||
try:
|
||||
self.model_updater(design_vars)
|
||||
@@ -318,6 +339,15 @@ class OptimizationRunner:
|
||||
print(f"Error updating model: {e}")
|
||||
raise optuna.TrialPruned()
|
||||
|
||||
# Execute post_mesh hooks (after model update)
|
||||
post_mesh_context = {
|
||||
'trial_number': trial.number,
|
||||
'design_variables': design_vars,
|
||||
'sim_file': self.config.get('sim_file', ''),
|
||||
'working_dir': str(Path.cwd())
|
||||
}
|
||||
self.hook_manager.execute_hooks('post_mesh', post_mesh_context, fail_fast=False)
|
||||
|
||||
# 3. Run simulation
|
||||
try:
|
||||
result_path = self.simulation_runner()
|
||||
@@ -325,6 +355,15 @@ class OptimizationRunner:
|
||||
print(f"Error running simulation: {e}")
|
||||
raise optuna.TrialPruned()
|
||||
|
||||
# Execute post_solve hooks
|
||||
post_solve_context = {
|
||||
'trial_number': trial.number,
|
||||
'design_variables': design_vars,
|
||||
'result_path': str(result_path) if result_path else '',
|
||||
'working_dir': str(Path.cwd())
|
||||
}
|
||||
self.hook_manager.execute_hooks('post_solve', post_solve_context, fail_fast=False)
|
||||
|
||||
# 4. Extract results with appropriate precision
|
||||
extracted_results = {}
|
||||
for obj in self.config['objectives']:
|
||||
@@ -362,6 +401,16 @@ class OptimizationRunner:
|
||||
print(f"Error extracting {const['name']}: {e}")
|
||||
raise optuna.TrialPruned()
|
||||
|
||||
# Execute post_extraction hooks
|
||||
post_extraction_context = {
|
||||
'trial_number': trial.number,
|
||||
'design_variables': design_vars,
|
||||
'extracted_results': extracted_results,
|
||||
'result_path': str(result_path) if result_path else '',
|
||||
'working_dir': str(Path.cwd())
|
||||
}
|
||||
self.hook_manager.execute_hooks('post_extraction', post_extraction_context, fail_fast=False)
|
||||
|
||||
# 5. Evaluate constraints
|
||||
for const in self.config.get('constraints', []):
|
||||
value = extracted_results[const['name']]
|
||||
@@ -389,6 +438,23 @@ class OptimizationRunner:
|
||||
else: # maximize
|
||||
total_objective -= weight * value
|
||||
|
||||
# Execute custom_objective hooks (can modify total_objective)
|
||||
custom_objective_context = {
|
||||
'trial_number': trial.number,
|
||||
'design_variables': design_vars,
|
||||
'extracted_results': extracted_results,
|
||||
'total_objective': total_objective,
|
||||
'working_dir': str(Path.cwd())
|
||||
}
|
||||
custom_results = self.hook_manager.execute_hooks('custom_objective', custom_objective_context, fail_fast=False)
|
||||
|
||||
# Allow hooks to override objective value
|
||||
for result in custom_results:
|
||||
if result and 'total_objective' in result:
|
||||
total_objective = result['total_objective']
|
||||
print(f"Custom objective hook modified total_objective to {total_objective:.6f}")
|
||||
break # Use first hook that provides override
|
||||
|
||||
# 7. Store results in history
|
||||
history_entry = {
|
||||
'trial_number': trial.number,
|
||||
|
||||
Reference in New Issue
Block a user