2025-11-16 21:29:54 -05:00
|
|
|
"""
|
|
|
|
|
Full End-to-End Bracket Optimization Test - Phase 3.2
|
|
|
|
|
|
|
|
|
|
This test demonstrates the complete LLM-enhanced optimization workflow:
|
|
|
|
|
1. LLM workflow configuration
|
|
|
|
|
2. Automatic extractor generation (displacement + stress)
|
|
|
|
|
3. Inline calculation generation (safety factor)
|
|
|
|
|
4. Manual safety factor constraint hook
|
|
|
|
|
5. Real NX simulation execution
|
|
|
|
|
6. OP2 result extraction
|
|
|
|
|
7. Constraint checking
|
|
|
|
|
8. Optimization with Optuna
|
|
|
|
|
9. Report generation
|
|
|
|
|
|
|
|
|
|
Objective: Maximize displacement
|
|
|
|
|
Constraint: Safety factor >= 4.0 (stress < yield/4)
|
|
|
|
|
Material: Aluminum 6061-T6 (Yield = 276 MPa)
|
|
|
|
|
Design Variable: wall_thickness (3-8mm)
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import sys
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
import json
|
|
|
|
|
|
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
|
|
|
|
refactor: Major reorganization of optimization_engine module structure
BREAKING CHANGE: Module paths have been reorganized for better maintainability.
Backwards compatibility aliases with deprecation warnings are provided.
New Structure:
- core/ - Optimization runners (runner, intelligent_optimizer, etc.)
- processors/ - Data processing
- surrogates/ - Neural network surrogates
- nx/ - NX/Nastran integration (solver, updater, session_manager)
- study/ - Study management (creator, wizard, state, reset)
- reporting/ - Reports and analysis (visualizer, report_generator)
- config/ - Configuration management (manager, builder)
- utils/ - Utilities (logger, auto_doc, etc.)
- future/ - Research/experimental code
Migration:
- ~200 import changes across 125 files
- All __init__.py files use lazy loading to avoid circular imports
- Backwards compatibility layer supports old import paths with warnings
- All existing functionality preserved
To migrate existing code:
OLD: from optimization_engine.nx_solver import NXSolver
NEW: from optimization_engine.nx.solver import NXSolver
OLD: from optimization_engine.runner import OptimizationRunner
NEW: from optimization_engine.core.runner import OptimizationRunner
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 12:30:59 -05:00
|
|
|
from optimization_engine.future.llm_optimization_runner import LLMOptimizationRunner
|
|
|
|
|
from optimization_engine.nx.solver import NXSolver
|
|
|
|
|
from optimization_engine.nx.updater import NXParameterUpdater
|
2025-11-16 21:29:54 -05:00
|
|
|
|
|
|
|
|
# LLM workflow for bracket optimization
|
|
|
|
|
llm_workflow = {
|
|
|
|
|
'engineering_features': [
|
|
|
|
|
{
|
|
|
|
|
'action': 'extract_displacement',
|
|
|
|
|
'domain': 'result_extraction',
|
|
|
|
|
'description': 'Extract displacement results from OP2 file',
|
|
|
|
|
'params': {'result_type': 'displacement'}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'action': 'extract_solid_stress',
|
|
|
|
|
'domain': 'result_extraction',
|
|
|
|
|
'description': 'Extract von Mises stress from solid elements',
|
|
|
|
|
'params': {
|
|
|
|
|
'result_type': 'stress',
|
|
|
|
|
'element_type': 'chexa' # Bracket uses CHEXA elements, not CTETRA
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
'inline_calculations': [
|
|
|
|
|
{
|
|
|
|
|
'action': 'calculate_safety_factor',
|
|
|
|
|
'params': {
|
|
|
|
|
'input': 'max_von_mises',
|
|
|
|
|
'yield_strength': 276.0, # MPa for Aluminum 6061-T6
|
|
|
|
|
'operation': 'divide'
|
|
|
|
|
},
|
|
|
|
|
'code_hint': 'safety_factor = 276.0 / max_von_mises'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'action': 'negate_displacement',
|
|
|
|
|
'params': {
|
|
|
|
|
'input': 'max_displacement',
|
|
|
|
|
'operation': 'negate'
|
|
|
|
|
},
|
|
|
|
|
'code_hint': 'neg_displacement = -max_displacement'
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
'post_processing_hooks': [], # Using manual safety_factor_constraint hook
|
|
|
|
|
'optimization': {
|
|
|
|
|
'algorithm': 'TPE',
|
|
|
|
|
'direction': 'minimize', # Minimize neg_displacement = maximize displacement
|
|
|
|
|
'design_variables': [
|
|
|
|
|
{
|
|
|
|
|
'parameter': 'tip_thickness',
|
|
|
|
|
'min': 15.0,
|
|
|
|
|
'max': 25.0,
|
|
|
|
|
'units': 'mm'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'parameter': 'support_angle',
|
|
|
|
|
'min': 20.0,
|
|
|
|
|
'max': 40.0,
|
|
|
|
|
'units': 'degrees'
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_model_updater(prt_file: Path):
|
|
|
|
|
"""Create model updater for NX."""
|
|
|
|
|
updater = NXParameterUpdater(prt_file_path=prt_file)
|
|
|
|
|
|
|
|
|
|
def update_model(design_vars: dict):
|
|
|
|
|
"""Update NX model with design variables."""
|
|
|
|
|
updater.update_expressions(design_vars)
|
|
|
|
|
updater.save() # Save changes to disk
|
|
|
|
|
|
|
|
|
|
return update_model
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_simulation_runner(sim_file: Path):
|
|
|
|
|
"""Create simulation runner for NX."""
|
|
|
|
|
solver = NXSolver(nastran_version='2412', use_journal=True)
|
|
|
|
|
|
|
|
|
|
def run_simulation() -> Path:
|
|
|
|
|
"""Run NX simulation and return OP2 file path."""
|
|
|
|
|
result = solver.run_simulation(sim_file)
|
|
|
|
|
return result['op2_file'] # Extract Path from result dict
|
|
|
|
|
|
|
|
|
|
return run_simulation
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def generate_report(results: dict, output_dir: Path):
|
|
|
|
|
"""Generate optimization report with history."""
|
|
|
|
|
report_file = output_dir / "optimization_report.md"
|
|
|
|
|
|
|
|
|
|
best_params = results['best_params']
|
|
|
|
|
best_value = results['best_value']
|
|
|
|
|
history = results['history']
|
|
|
|
|
|
|
|
|
|
with open(report_file, 'w') as f:
|
|
|
|
|
f.write("# Bracket Optimization Report - Phase 3.2\n\n")
|
|
|
|
|
f.write(f"**Generated**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
|
|
|
|
|
|
|
|
|
|
f.write("## Problem Definition\n\n")
|
|
|
|
|
f.write("- **Objective**: Maximize displacement\n")
|
|
|
|
|
f.write("- **Constraint**: Safety factor >= 4.0\n")
|
|
|
|
|
f.write("- **Material**: Aluminum 6061-T6 (Yield = 276 MPa)\n")
|
|
|
|
|
f.write("- **Allowable Stress**: 69 MPa (276/4)\n")
|
|
|
|
|
f.write("- **Design Variables**:\n")
|
|
|
|
|
f.write(" - tip_thickness (15-25 mm)\n")
|
|
|
|
|
f.write(" - support_angle (20-40 degrees)\n\n")
|
|
|
|
|
|
|
|
|
|
f.write("## Optimization Results\n\n")
|
|
|
|
|
f.write(f"- **Best tip_thickness**: {best_params['tip_thickness']:.3f} mm\n")
|
|
|
|
|
f.write(f"- **Best support_angle**: {best_params['support_angle']:.3f} degrees\n")
|
|
|
|
|
f.write(f"- **Best objective value**: {best_value:.6f}\n")
|
|
|
|
|
f.write(f"- **Total trials**: {len(history)}\n\n")
|
|
|
|
|
|
|
|
|
|
# Find best trial details
|
|
|
|
|
best_trial = history[results['best_trial_number']]
|
|
|
|
|
best_results = best_trial['results']
|
|
|
|
|
best_calcs = best_trial['calculations']
|
|
|
|
|
|
|
|
|
|
f.write("## Best Design Details\n\n")
|
|
|
|
|
f.write(f"- **Max displacement**: {best_results.get('max_displacement', 0):.6f} mm\n")
|
|
|
|
|
f.write(f"- **Max stress**: {best_results.get('max_von_mises', 0):.3f} MPa\n")
|
|
|
|
|
f.write(f"- **Safety factor**: {best_calcs.get('safety_factor', 0):.3f}\n")
|
|
|
|
|
f.write(f"- **Constraint status**: {'SATISFIED' if best_calcs.get('safety_factor', 0) >= 4.0 else 'VIOLATED'}\n\n")
|
|
|
|
|
|
|
|
|
|
f.write("## Optimization History\n\n")
|
|
|
|
|
f.write("| Trial | Tip Thick (mm) | Support Angle (°) | Displacement (mm) | Stress (MPa) | Safety Factor | Constraint | Objective |\n")
|
|
|
|
|
f.write("|-------|----------------|-------------------|-------------------|--------------|---------------|------------|-----------|\n")
|
|
|
|
|
|
|
|
|
|
for trial in history:
|
|
|
|
|
trial_num = trial['trial_number']
|
|
|
|
|
tip_thick = trial['design_variables']['tip_thickness']
|
|
|
|
|
support_ang = trial['design_variables']['support_angle']
|
|
|
|
|
disp = trial['results'].get('max_displacement', 0)
|
|
|
|
|
stress = trial['results'].get('max_von_mises', 0)
|
|
|
|
|
sf = trial['calculations'].get('safety_factor', 0)
|
|
|
|
|
constraint = 'OK' if sf >= 4.0 else 'FAIL'
|
|
|
|
|
obj = trial['objective']
|
|
|
|
|
|
|
|
|
|
f.write(f"| {trial_num} | {tip_thick:.3f} | {support_ang:.3f} | {disp:.6f} | {stress:.3f} | {sf:.3f} | {constraint} | {obj:.6f} |\n")
|
|
|
|
|
|
|
|
|
|
f.write("\n## LLM-Enhanced Workflow\n\n")
|
|
|
|
|
f.write("This optimization was run using the Phase 3.2 LLM-enhanced runner with:\n\n")
|
|
|
|
|
f.write("- **Automatic extractor generation**: Displacement + Stress (Phase 3.1)\n")
|
|
|
|
|
f.write("- **Inline calculations**: Safety factor + Objective negation (Phase 2.8)\n")
|
|
|
|
|
f.write("- **Manual constraint hook**: Safety factor constraint (demonstrates flexibility)\n")
|
|
|
|
|
f.write("- **Real NX simulation**: Journal-based solver execution\n")
|
|
|
|
|
f.write("- **Optuna optimization**: TPE sampler\n\n")
|
|
|
|
|
|
|
|
|
|
f.write("---\n\n")
|
|
|
|
|
f.write("*Generated by Atomizer Phase 3.2 - LLM-Enhanced Optimization Framework*\n")
|
|
|
|
|
|
|
|
|
|
print(f"\nReport saved to: {report_file}")
|
|
|
|
|
return report_file
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
print("=" * 80)
|
|
|
|
|
print("Phase 3.2: Full End-to-End Bracket Optimization Test")
|
|
|
|
|
print("=" * 80)
|
|
|
|
|
print()
|
|
|
|
|
print("Problem:")
|
|
|
|
|
print(" Maximize displacement while maintaining safety factor >= 4.0")
|
|
|
|
|
print(" Material: Aluminum 6061-T6 (Yield = 276 MPa)")
|
|
|
|
|
print(" Design Variables:")
|
|
|
|
|
print(" - tip_thickness (15-25 mm)")
|
|
|
|
|
print(" - support_angle (20-40 degrees)")
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
# Configuration
|
|
|
|
|
sim_file = Path("tests/Bracket_sim1.sim")
|
|
|
|
|
prt_file = Path("tests/Bracket.prt")
|
|
|
|
|
n_trials = 5 # Start with 5 trials for testing
|
|
|
|
|
|
|
|
|
|
if not sim_file.exists():
|
|
|
|
|
print(f"ERROR: Simulation file not found: {sim_file}")
|
|
|
|
|
print("Please ensure the bracket .sim file is available")
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
if not prt_file.exists():
|
|
|
|
|
print(f"ERROR: Part file not found: {prt_file}")
|
|
|
|
|
print("Please ensure the bracket .prt file is available")
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
print(f"Simulation file: {sim_file}")
|
|
|
|
|
print(f"Part file: {prt_file}")
|
|
|
|
|
print(f"Number of trials: {n_trials}")
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
# Create model updater and simulation runner
|
|
|
|
|
print("Setting up NX integration...")
|
|
|
|
|
model_updater = create_model_updater(prt_file)
|
|
|
|
|
simulation_runner = create_simulation_runner(sim_file)
|
|
|
|
|
print(" NX updater: OK")
|
|
|
|
|
print(" NX solver: OK")
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
# Initialize LLM optimization runner
|
|
|
|
|
print("Initializing LLM-enhanced optimization runner...")
|
|
|
|
|
runner = LLMOptimizationRunner(
|
|
|
|
|
llm_workflow=llm_workflow,
|
|
|
|
|
model_updater=model_updater,
|
|
|
|
|
simulation_runner=simulation_runner,
|
|
|
|
|
study_name='bracket_maximize_disp_sf4'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
print(f" Extractors generated: {len(runner.extractors)}")
|
|
|
|
|
for ext in runner.extractors:
|
|
|
|
|
print(f" - {ext}")
|
|
|
|
|
print(f" Inline calculations: {len(runner.inline_code)}")
|
|
|
|
|
hook_summary = runner.hook_manager.get_summary()
|
|
|
|
|
print(f" Hooks loaded: {hook_summary['enabled_hooks']}")
|
|
|
|
|
print(" (Including manual safety_factor_constraint hook)")
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
# Run optimization
|
|
|
|
|
print("=" * 80)
|
|
|
|
|
print("Starting Optimization...")
|
|
|
|
|
print("=" * 80)
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
results = runner.run_optimization(n_trials=n_trials)
|
|
|
|
|
|
|
|
|
|
print()
|
|
|
|
|
print("=" * 80)
|
|
|
|
|
print("Optimization Complete!")
|
|
|
|
|
print("=" * 80)
|
|
|
|
|
print()
|
|
|
|
|
print(f"Best tip_thickness: {results['best_params']['tip_thickness']:.3f} mm")
|
|
|
|
|
print(f"Best support_angle: {results['best_params']['support_angle']:.3f} degrees")
|
|
|
|
|
print(f"Best objective value: {results['best_value']:.6f}")
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
# Generate report
|
|
|
|
|
print("Generating optimization report...")
|
|
|
|
|
report_file = generate_report(results, runner.output_dir)
|
|
|
|
|
|
|
|
|
|
print()
|
|
|
|
|
print("=" * 80)
|
|
|
|
|
print("Test Complete!")
|
|
|
|
|
print("=" * 80)
|
|
|
|
|
print(f"Results directory: {runner.output_dir}")
|
|
|
|
|
print(f"Report: {report_file}")
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"\nERROR during optimization: {e}")
|
|
|
|
|
import traceback
|
|
|
|
|
traceback.print_exc()
|
|
|
|
|
sys.exit(1)
|