214 lines
6.7 KiB
Python
214 lines
6.7 KiB
Python
|
|
"""
|
||
|
|
{Hook Name} - Lifecycle Hook Plugin
|
||
|
|
|
||
|
|
This is a template for creating new lifecycle hooks.
|
||
|
|
Copy this file to optimization_engine/plugins/{hook_point}/{hook_name}.py
|
||
|
|
|
||
|
|
Available hook points:
|
||
|
|
- pre_mesh: Before meshing
|
||
|
|
- post_mesh: After meshing
|
||
|
|
- pre_solve: Before solver execution
|
||
|
|
- post_solve: After solver completion
|
||
|
|
- post_extraction: After result extraction
|
||
|
|
- post_calculation: After objective calculation
|
||
|
|
- custom_objective: Custom objective functions
|
||
|
|
|
||
|
|
Author: {Your Name}
|
||
|
|
Created: {Date}
|
||
|
|
Version: 1.0
|
||
|
|
Hook Point: {hook_point}
|
||
|
|
"""
|
||
|
|
|
||
|
|
from typing import Dict, Any, Optional
|
||
|
|
from pathlib import Path
|
||
|
|
import json
|
||
|
|
from datetime import datetime
|
||
|
|
|
||
|
|
|
||
|
|
def {hook_name}_hook(context: Dict[str, Any]) -> Dict[str, Any]:
|
||
|
|
"""
|
||
|
|
{Description of what this hook does}.
|
||
|
|
|
||
|
|
This hook runs at the {hook_point} stage of the optimization trial.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
context: Dictionary containing trial context:
|
||
|
|
- trial_number (int): Current trial number
|
||
|
|
- design_params (dict): Current design parameter values
|
||
|
|
- config (dict): Optimization configuration
|
||
|
|
- working_dir (Path): Study working directory
|
||
|
|
|
||
|
|
For post_solve and later:
|
||
|
|
- op2_file (Path): Path to OP2 results file
|
||
|
|
- solve_success (bool): Whether solve succeeded
|
||
|
|
- solve_time (float): Solve duration in seconds
|
||
|
|
|
||
|
|
For post_extraction and later:
|
||
|
|
- results (dict): Extracted results so far
|
||
|
|
|
||
|
|
For post_calculation:
|
||
|
|
- objectives (dict): Computed objective values
|
||
|
|
- constraints (dict): Constraint values
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Dictionary with computed values or modifications.
|
||
|
|
These values are added to the trial context.
|
||
|
|
Return empty dict {} if no modifications needed.
|
||
|
|
|
||
|
|
Raises:
|
||
|
|
Exception: Any exception will be logged but won't stop the trial
|
||
|
|
unless you want it to (raise optuna.TrialPruned instead)
|
||
|
|
|
||
|
|
Example:
|
||
|
|
>>> context = {'trial_number': 1, 'design_params': {'x': 5.0}}
|
||
|
|
>>> result = {hook_name}_hook(context)
|
||
|
|
>>> print(result)
|
||
|
|
{{'{computed_key}': 123.45}}
|
||
|
|
"""
|
||
|
|
# =========================================
|
||
|
|
# Access context values
|
||
|
|
# =========================================
|
||
|
|
trial_num = context.get('trial_number', 0)
|
||
|
|
design_params = context.get('design_params', {})
|
||
|
|
config = context.get('config', {})
|
||
|
|
working_dir = context.get('working_dir', Path('.'))
|
||
|
|
|
||
|
|
# For post_solve hooks and later:
|
||
|
|
# op2_file = context.get('op2_file')
|
||
|
|
# solve_success = context.get('solve_success', False)
|
||
|
|
|
||
|
|
# For post_extraction hooks and later:
|
||
|
|
# results = context.get('results', {})
|
||
|
|
|
||
|
|
# For post_calculation hooks:
|
||
|
|
# objectives = context.get('objectives', {})
|
||
|
|
# constraints = context.get('constraints', {})
|
||
|
|
|
||
|
|
# =========================================
|
||
|
|
# Your hook logic here
|
||
|
|
# =========================================
|
||
|
|
|
||
|
|
# Example: Log trial start (pre_solve hook)
|
||
|
|
# print(f"[Hook] Trial {trial_num} starting with params: {design_params}")
|
||
|
|
|
||
|
|
# Example: Compute derived quantity (post_extraction hook)
|
||
|
|
# max_stress = results.get('max_von_mises', 0)
|
||
|
|
# yield_strength = config.get('material', {}).get('yield_strength', 250)
|
||
|
|
# safety_factor = yield_strength / max(max_stress, 1e-6)
|
||
|
|
|
||
|
|
# Example: Write log file (post_calculation hook)
|
||
|
|
# log_entry = {
|
||
|
|
# 'trial': trial_num,
|
||
|
|
# 'timestamp': datetime.now().isoformat(),
|
||
|
|
# 'objectives': context.get('objectives', {}),
|
||
|
|
# }
|
||
|
|
# with open(working_dir / 'trial_log.jsonl', 'a') as f:
|
||
|
|
# f.write(json.dumps(log_entry) + '\n')
|
||
|
|
|
||
|
|
# =========================================
|
||
|
|
# Return computed values
|
||
|
|
# =========================================
|
||
|
|
|
||
|
|
# Values returned here are added to the context
|
||
|
|
# and can be accessed by later hooks or the optimizer
|
||
|
|
|
||
|
|
return {
|
||
|
|
# '{computed_key}': computed_value,
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def register_hooks(hook_manager) -> None:
|
||
|
|
"""
|
||
|
|
Register this hook with the hook manager.
|
||
|
|
|
||
|
|
This function is called automatically when plugins are discovered.
|
||
|
|
It must be named exactly 'register_hooks' and take one argument.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
hook_manager: The HookManager instance from optimization_engine
|
||
|
|
"""
|
||
|
|
hook_manager.register_hook(
|
||
|
|
hook_point='{hook_point}', # pre_mesh, post_mesh, pre_solve, etc.
|
||
|
|
function={hook_name}_hook,
|
||
|
|
name='{hook_name}_hook',
|
||
|
|
description='{Brief description of what this hook does}',
|
||
|
|
priority=100, # Lower number = runs earlier (1-200 typical range)
|
||
|
|
enabled=True # Set to False to disable by default
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
# =========================================
|
||
|
|
# Optional: Helper functions
|
||
|
|
# =========================================
|
||
|
|
|
||
|
|
def _helper_function(data: Any) -> Any:
|
||
|
|
"""
|
||
|
|
Private helper function for the hook.
|
||
|
|
|
||
|
|
Keep hook logic clean by extracting complex operations
|
||
|
|
into helper functions.
|
||
|
|
"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
|
||
|
|
# =========================================
|
||
|
|
# After creating your hook:
|
||
|
|
#
|
||
|
|
# 1. Place in correct directory:
|
||
|
|
# optimization_engine/plugins/{hook_point}/{hook_name}.py
|
||
|
|
#
|
||
|
|
# 2. Hook is auto-discovered - no __init__.py changes needed
|
||
|
|
#
|
||
|
|
# 3. Test the hook:
|
||
|
|
# python -c "
|
||
|
|
# from optimization_engine.plugins.hook_manager import HookManager
|
||
|
|
# hm = HookManager()
|
||
|
|
# hm.discover_plugins()
|
||
|
|
# print(hm.list_hooks())
|
||
|
|
# "
|
||
|
|
#
|
||
|
|
# 4. Update documentation if significant:
|
||
|
|
# - Add to EXT_02_CREATE_HOOK.md examples section
|
||
|
|
# =========================================
|
||
|
|
|
||
|
|
|
||
|
|
# =========================================
|
||
|
|
# Example hooks for reference
|
||
|
|
# =========================================
|
||
|
|
|
||
|
|
def example_logger_hook(context: Dict[str, Any]) -> Dict[str, Any]:
|
||
|
|
"""Example: Simple trial logger for pre_solve."""
|
||
|
|
trial = context.get('trial_number', 0)
|
||
|
|
params = context.get('design_params', {})
|
||
|
|
print(f"[LOG] Trial {trial} starting: {params}")
|
||
|
|
return {}
|
||
|
|
|
||
|
|
|
||
|
|
def example_safety_factor_hook(context: Dict[str, Any]) -> Dict[str, Any]:
|
||
|
|
"""Example: Safety factor calculator for post_extraction."""
|
||
|
|
results = context.get('results', {})
|
||
|
|
max_stress = results.get('max_von_mises', 0)
|
||
|
|
|
||
|
|
if max_stress > 0:
|
||
|
|
safety_factor = 250.0 / max_stress # Assuming 250 MPa yield
|
||
|
|
else:
|
||
|
|
safety_factor = float('inf')
|
||
|
|
|
||
|
|
return {'safety_factor': safety_factor}
|
||
|
|
|
||
|
|
|
||
|
|
def example_validator_hook(context: Dict[str, Any]) -> Dict[str, Any]:
|
||
|
|
"""Example: Result validator for post_solve."""
|
||
|
|
import optuna
|
||
|
|
|
||
|
|
solve_success = context.get('solve_success', False)
|
||
|
|
op2_file = context.get('op2_file')
|
||
|
|
|
||
|
|
if not solve_success:
|
||
|
|
raise optuna.TrialPruned("Solve failed")
|
||
|
|
|
||
|
|
if op2_file and not Path(op2_file).exists():
|
||
|
|
raise optuna.TrialPruned("OP2 file not generated")
|
||
|
|
|
||
|
|
return {'validation_passed': True}
|