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:
2025-11-15 14:46:49 -05:00
parent 0ce9ddf3e2
commit a24e3f750c
14 changed files with 1473 additions and 0 deletions

View File

@@ -0,0 +1,243 @@
{
"version": "1.0.0",
"last_updated": "2025-01-15",
"description": "Registry of all Atomizer capabilities for LLM discovery and usage",
"core_features": {
"optimization": {
"description": "Core optimization engine using Optuna",
"module": "optimization_engine.runner",
"capabilities": [
"Multi-objective optimization with weighted sum",
"TPE (Tree-structured Parzen Estimator) sampler",
"CMA-ES sampler",
"Gaussian Process sampler",
"50-trial default with 20 startup trials",
"Automatic checkpoint and resume",
"SQLite-based study persistence"
],
"usage": "python examples/test_journal_optimization.py",
"llm_hint": "Use this for Bayesian optimization with NX simulations"
},
"nx_integration": {
"description": "Siemens NX simulation automation via journal scripts",
"module": "optimization_engine.nx_solver",
"capabilities": [
"Update CAD expressions via NXOpen",
"Execute NX Nastran solver",
"Extract OP2 results (stress, displacement)",
"Extract mass properties",
"Precision control (4 decimals for mm/degrees/MPa)"
],
"usage": "from optimization_engine.nx_solver import run_nx_simulation",
"llm_hint": "Use for running FEA simulations and extracting results"
},
"result_extraction": {
"description": "Extract metrics from simulation results",
"module": "optimization_engine.result_extractors",
"extractors": {
"stress_extractor": {
"description": "Extract stress data from OP2 files",
"metrics": ["max_von_mises", "mean_von_mises", "max_principal"],
"file_type": "OP2",
"usage": "Returns stress in MPa"
},
"displacement_extractor": {
"description": "Extract displacement data from OP2 files",
"metrics": ["max_displacement", "mean_displacement"],
"file_type": "OP2",
"usage": "Returns displacement in mm"
},
"mass_extractor": {
"description": "Extract mass properties",
"metrics": ["total_mass", "center_of_gravity"],
"file_type": "NX Part",
"usage": "Returns mass in kg"
}
},
"llm_hint": "Use extractors to define objectives and constraints"
}
},
"plugin_system": {
"description": "Extensible hook system for custom functionality",
"module": "optimization_engine.plugins",
"version": "1.0.0",
"hook_points": {
"pre_mesh": {
"description": "Execute before meshing operations",
"context": ["trial_number", "design_variables", "sim_file", "working_dir"],
"use_cases": [
"Modify geometry based on parameters",
"Set up boundary conditions",
"Configure mesh settings"
]
},
"post_mesh": {
"description": "Execute after meshing, before solve",
"context": ["trial_number", "mesh_info", "element_count", "node_count"],
"use_cases": [
"Validate mesh quality",
"Export mesh for visualization",
"Log mesh statistics"
]
},
"pre_solve": {
"description": "Execute before solver launch",
"context": ["trial_number", "design_variables", "solver_settings"],
"use_cases": [
"Log trial parameters",
"Modify solver settings",
"Set up custom load cases"
]
},
"post_solve": {
"description": "Execute after solve, before result extraction",
"context": ["trial_number", "solve_status", "output_files"],
"use_cases": [
"Check solver convergence",
"Post-process results",
"Generate visualizations"
]
},
"post_extraction": {
"description": "Execute after result extraction",
"context": ["trial_number", "extracted_results", "objectives", "constraints"],
"use_cases": [
"Calculate custom metrics",
"Combine multiple objectives (RSS)",
"Apply penalty functions"
]
},
"custom_objective": {
"description": "Define custom objective functions",
"context": ["extracted_results", "design_variables"],
"use_cases": [
"RSS of stress and displacement",
"Weighted multi-criteria",
"Conditional objectives"
]
}
},
"api": {
"register_hook": {
"description": "Register a new hook function",
"signature": "hook_manager.register_hook(hook_point, function, description, name=None, priority=100)",
"parameters": {
"hook_point": "One of: pre_mesh, post_mesh, pre_solve, post_solve, post_extraction, custom_objective",
"function": "Callable[[Dict[str, Any]], Optional[Dict[str, Any]]]",
"description": "Human-readable description",
"priority": "Execution order (lower = earlier)"
},
"example": "See optimization_engine/plugins/pre_solve/log_trial_start.py"
},
"execute_hooks": {
"description": "Execute all hooks at a specific point",
"signature": "hook_manager.execute_hooks(hook_point, context, fail_fast=False)",
"returns": "List of hook results"
}
},
"validators": {
"validate_plugin_code": {
"description": "Validate plugin code for safety",
"checks": [
"Syntax errors",
"Dangerous imports (os.system, subprocess, etc.)",
"File operations (optional allow)",
"Function signature correctness"
],
"safe_modules": ["math", "numpy", "scipy", "pandas", "pathlib", "json", "optuna", "pyNastran"],
"llm_hint": "Always validate LLM-generated code before execution"
}
}
},
"design_variables": {
"description": "Parametric CAD variables to optimize",
"schema": {
"name": "Unique identifier",
"expression_name": "NX expression name",
"min": "Lower bound (float)",
"max": "Upper bound (float)",
"units": "Unit system (mm, degrees, etc.)"
},
"example": {
"name": "wall_thickness",
"expression_name": "wall_thickness",
"min": 3.0,
"max": 8.0,
"units": "mm"
}
},
"objectives": {
"description": "Metrics to minimize or maximize",
"schema": {
"name": "Unique identifier",
"extractor": "Result extractor to use",
"metric": "Specific metric from extractor",
"direction": "minimize or maximize",
"weight": "Importance (for multi-objective)",
"units": "Unit system"
},
"example": {
"name": "max_stress",
"extractor": "stress_extractor",
"metric": "max_von_mises",
"direction": "minimize",
"weight": 1.0,
"units": "MPa"
}
},
"constraints": {
"description": "Limits on simulation outputs",
"schema": {
"name": "Unique identifier",
"extractor": "Result extractor to use",
"metric": "Specific metric",
"type": "upper_bound or lower_bound",
"limit": "Constraint value",
"units": "Unit system"
},
"example": {
"name": "max_displacement_limit",
"extractor": "displacement_extractor",
"metric": "max_displacement",
"type": "upper_bound",
"limit": 1.0,
"units": "mm"
}
},
"examples": {
"bracket_optimization": {
"description": "Minimize stress on a bracket by varying wall thickness",
"location": "examples/bracket/",
"design_variables": ["wall_thickness"],
"objectives": ["max_von_mises"],
"trials": 50,
"typical_runtime": "2-3 hours",
"llm_hint": "Good template for single-objective structural optimization"
}
},
"llm_guidelines": {
"code_generation": {
"hook_template": "Always include: function signature with context dict, docstring, return dict",
"validation": "Use validate_plugin_code() before registration",
"error_handling": "Wrap in try-except, log errors, return None on failure"
},
"natural_language_mapping": {
"minimize stress": "objective with direction='minimize', extractor='stress_extractor'",
"vary thickness 3-8mm": "design_variable with min=3.0, max=8.0, units='mm'",
"displacement < 1mm": "constraint with type='upper_bound', limit=1.0",
"RSS of stress and displacement": "custom_objective hook with sqrt(stress² + displacement²)"
}
}
}