Files
Atomizer/docs/protocols/extensions/EXT_02_CREATE_HOOK.md
Antoine 602560c46a feat: Add MLP surrogate with Turbo Mode for 100x faster optimization
Neural Acceleration (MLP Surrogate):
- Add run_nn_optimization.py with hybrid FEA/NN workflow
- MLP architecture: 4-layer (64->128->128->64) with BatchNorm/Dropout
- Three workflow modes:
  - --all: Sequential export->train->optimize->validate
  - --hybrid-loop: Iterative Train->NN->Validate->Retrain cycle
  - --turbo: Aggressive single-best validation (RECOMMENDED)
- Turbo mode: 5000 NN trials + 50 FEA validations in ~12 minutes
- Separate nn_study.db to avoid overloading dashboard

Performance Results (bracket_pareto_3obj study):
- NN prediction errors: mass 1-5%, stress 1-4%, stiffness 5-15%
- Found minimum mass designs at boundary (angle~30deg, thick~30mm)
- 100x speedup vs pure FEA exploration

Protocol Operating System:
- Add .claude/skills/ with Bootstrap, Cheatsheet, Context Loader
- Add docs/protocols/ with operations (OP_01-06) and system (SYS_10-14)
- Update SYS_14_NEURAL_ACCELERATION.md with MLP Turbo Mode docs

NX Automation:
- Add optimization_engine/hooks/ for NX CAD/CAE automation
- Add study_wizard.py for guided study creation
- Fix FEM mesh update: load idealized part before UpdateFemodel()

New Study:
- bracket_pareto_3obj: 3-objective Pareto (mass, stress, stiffness)
- 167 FEA trials + 5000 NN trials completed
- Demonstrates full hybrid workflow

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-06 20:01:59 -05:00

8.8 KiB

EXT_02: Create Lifecycle Hook

Overview

This protocol guides you through creating lifecycle hooks that execute at specific points during optimization. Hooks enable custom logic injection without modifying core code.

Privilege Required: power_user or admin


When to Use

Trigger Action
Need custom logic at specific point Follow this protocol
"create hook", "callback" Follow this protocol
Want to log/validate/modify at runtime Follow this protocol

Quick Reference

Hook Points Available:

Hook Point When It Runs Use Case
pre_mesh Before meshing Validate geometry
post_mesh After meshing Check mesh quality
pre_solve Before solver Log trial start
post_solve After solver Validate results
post_extraction After extraction Custom metrics
post_calculation After objectives Derived quantities
custom_objective Custom objective Complex objectives

Create in: optimization_engine/plugins/{hook_point}/


Step-by-Step Guide

Step 1: Identify Hook Point

Choose the appropriate hook point:

Trial Flow:
    │
    ├─► PRE_MESH         → Validate model before meshing
    │
    ├─► POST_MESH        → Check mesh quality
    │
    ├─► PRE_SOLVE        → Log trial start, validate inputs
    │
    ├─► POST_SOLVE       → Check solve success, capture timing
    │
    ├─► POST_EXTRACTION  → Compute derived quantities
    │
    ├─► POST_CALCULATION → Final validation, logging
    │
    └─► CUSTOM_OBJECTIVE → Custom objective functions

Step 2: Create Hook File

Create optimization_engine/plugins/{hook_point}/{hook_name}.py:

"""
{Hook Description}

Author: {Your Name}
Created: {Date}
Version: 1.0
Hook Point: {hook_point}
"""

from typing import Dict, Any


def {hook_name}_hook(context: Dict[str, Any]) -> Dict[str, Any]:
    """
    {Description of what this hook does}.

    Args:
        context: Dictionary containing:
            - trial_number: Current trial number
            - design_params: Current design parameters
            - results: Results so far (if post-extraction)
            - config: Optimization config
            - working_dir: Path to working directory

    Returns:
        Dictionary with computed values or modifications.
        Return empty dict if no modifications needed.

    Example:
        >>> result = {hook_name}_hook({'trial_number': 1, ...})
        >>> print(result)
        {'{computed_key}': 123.45}
    """
    # Access context
    trial_num = context.get('trial_number')
    design_params = context.get('design_params', {})
    results = context.get('results', {})

    # Your logic here
    # ...

    # Return computed values
    return {
        '{computed_key}': computed_value,
    }


def register_hooks(hook_manager):
    """
    Register this hook with the hook manager.

    This function is called automatically when plugins are loaded.

    Args:
        hook_manager: The HookManager instance
    """
    hook_manager.register_hook(
        hook_point='{hook_point}',
        function={hook_name}_hook,
        name='{hook_name}_hook',
        description='{Brief description}',
        priority=100,  # Lower = runs earlier
        enabled=True
    )

Step 3: Test Hook

# Test in isolation
from optimization_engine.plugins.{hook_point}.{hook_name} import {hook_name}_hook

test_context = {
    'trial_number': 1,
    'design_params': {'thickness': 5.0},
    'results': {'max_stress': 200.0},
}

result = {hook_name}_hook(test_context)
print(result)

Step 4: Enable Hook

Hooks are auto-discovered from the plugins directory. To verify:

from optimization_engine.plugins.hook_manager import HookManager

manager = HookManager()
manager.discover_plugins()
print(manager.list_hooks())

Hook Examples

Example 1: Safety Factor Calculator (post_calculation)

"""Calculate safety factor after stress extraction."""

def safety_factor_hook(context):
    """Calculate safety factor from stress results."""
    results = context.get('results', {})
    config = context.get('config', {})

    max_stress = results.get('max_von_mises', 0)
    yield_strength = config.get('material', {}).get('yield_strength', 250)

    if max_stress > 0:
        safety_factor = yield_strength / max_stress
    else:
        safety_factor = float('inf')

    return {
        'safety_factor': safety_factor,
        'yield_strength': yield_strength,
    }


def register_hooks(hook_manager):
    hook_manager.register_hook(
        hook_point='post_calculation',
        function=safety_factor_hook,
        name='safety_factor_hook',
        description='Calculate safety factor from stress',
        priority=100,
        enabled=True
    )

Example 2: Trial Logger (pre_solve)

"""Log trial information before solve."""

import json
from datetime import datetime
from pathlib import Path


def trial_logger_hook(context):
    """Log trial start information."""
    trial_num = context.get('trial_number')
    design_params = context.get('design_params', {})
    working_dir = context.get('working_dir', Path('.'))

    log_entry = {
        'trial': trial_num,
        'timestamp': datetime.now().isoformat(),
        'params': design_params,
    }

    log_file = working_dir / 'trial_log.jsonl'
    with open(log_file, 'a') as f:
        f.write(json.dumps(log_entry) + '\n')

    return {}  # No modifications


def register_hooks(hook_manager):
    hook_manager.register_hook(
        hook_point='pre_solve',
        function=trial_logger_hook,
        name='trial_logger_hook',
        description='Log trial parameters before solve',
        priority=10,  # Run early
        enabled=True
    )

Example 3: Mesh Quality Check (post_mesh)

"""Validate mesh quality after meshing."""


def mesh_quality_hook(context):
    """Check mesh quality metrics."""
    mesh_file = context.get('mesh_file')

    # Check quality metrics
    quality_issues = []

    # ... quality checks ...

    if quality_issues:
        context['warnings'] = context.get('warnings', []) + quality_issues

    return {
        'mesh_quality_passed': len(quality_issues) == 0,
        'mesh_issues': quality_issues,
    }


def register_hooks(hook_manager):
    hook_manager.register_hook(
        hook_point='post_mesh',
        function=mesh_quality_hook,
        name='mesh_quality_hook',
        description='Validate mesh quality',
        priority=50,
        enabled=True
    )

Hook Context Reference

Standard Context Keys

Key Type Available At Description
trial_number int All Current trial number
design_params dict All Design parameter values
config dict All Optimization config
working_dir Path All Study working directory
model_file Path pre_mesh+ NX model file path
mesh_file Path post_mesh+ Mesh file path
op2_file Path post_solve+ Results file path
results dict post_extraction+ Extracted results
objectives dict post_calculation Computed objectives

Priority Guidelines

Priority Range Use For
1-50 Critical hooks that must run first
50-100 Standard hooks
100-150 Logging and monitoring
150+ Cleanup and finalization

Managing Hooks

Enable/Disable at Runtime

hook_manager.disable_hook('my_hook')
hook_manager.enable_hook('my_hook')

Check Hook Status

hooks = hook_manager.list_hooks()
for hook in hooks:
    print(f"{hook['name']}: {'enabled' if hook['enabled'] else 'disabled'}")

Hook Execution Order

Hooks at the same point run in priority order (lower first):

Priority 10: trial_logger_hook
Priority 50: mesh_quality_hook
Priority 100: safety_factor_hook

Troubleshooting

Issue Cause Solution
Hook not running Not registered Check register_hooks function
Wrong hook point Misnamed directory Check directory name matches hook point
Context missing key Wrong hook point Use appropriate hook point for data needed
Hook error crashes trial Unhandled exception Add try/except in hook

Cross-References

  • Related: EXT_01_CREATE_EXTRACTOR
  • System: optimization_engine/plugins/hook_manager.py
  • Template: templates/hook_template.py

Version History

Version Date Changes
1.0 2025-12-05 Initial release