Files
Atomizer/examples/run_optimization.py
Anto01 be3b9ee5d5 feat: Add complete optimization runner pipeline
Implement core optimization engine with:
- OptimizationRunner class with Optuna integration
- NXParameterUpdater for updating .prt file expressions
- Result extractor wrappers for OP2 files
- Complete end-to-end example workflow

Features:
- runner.py: Main optimization loop, multi-objective support, constraint handling
- nx_updater.py: Binary .prt file parameter updates (tested successfully)
- extractors.py: Wrappers for mass/stress/displacement extraction
- run_optimization.py: Complete example showing full workflow

NX Updater tested with bracket example:
- Successfully found 4 expressions (support_angle, tip_thickness, p3, support_blend_radius)
- Updated support_angle 30.0 -> 33.0 and verified

Next steps:
- Install pyNastran for OP2 extraction
- Integrate NX solver execution
- Replace dummy extractors with real OP2 readers

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-15 10:29:33 -05:00

207 lines
6.2 KiB
Python

"""
Example: Running Complete Optimization
This example demonstrates the complete optimization workflow:
1. Load optimization configuration
2. Update NX model parameters
3. Run simulation (dummy for now - would call NX solver)
4. Extract results from OP2
5. Optimize with Optuna
For a real run, you would need:
- pyNastran installed for OP2 extraction
- NX solver accessible to run simulations
"""
from pathlib import Path
import sys
# Add project root to path
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))
from optimization_engine.runner import OptimizationRunner
from optimization_engine.nx_updater import update_nx_model
# ==================================================
# STEP 1: Define model updater function
# ==================================================
def bracket_model_updater(design_vars: dict):
"""
Update the bracket model with new design variable values.
Args:
design_vars: Dict like {'tip_thickness': 22.5, 'support_angle': 35.0}
"""
prt_file = project_root / "examples/bracket/Bracket.prt"
print(f"\n[MODEL UPDATE] Updating {prt_file.name} with:")
for name, value in design_vars.items():
print(f" {name} = {value:.4f}")
# Update the .prt file with new parameter values
update_nx_model(prt_file, design_vars, backup=False)
print("[MODEL UPDATE] Complete")
# ==================================================
# STEP 2: Define simulation runner function
# ==================================================
def bracket_simulation_runner() -> Path:
"""
Run NX simulation and return path to result files.
In a real implementation, this would:
1. Open NX (or use batch mode)
2. Update the .sim file
3. Run the solver
4. Wait for completion
5. Return path to .op2 file
For now, we return the path to existing results.
"""
print("\n[SIMULATION] Running NX Nastran solver...")
print("[SIMULATION] (Using existing results for demonstration)")
# In real use, this would run the actual solver
# For now, return path to existing OP2 file
result_file = project_root / "examples/bracket/bracket_sim1-solution_1.op2"
if not result_file.exists():
raise FileNotFoundError(f"Result file not found: {result_file}")
print(f"[SIMULATION] Results: {result_file.name}")
return result_file
# ==================================================
# STEP 3: Define result extractors (dummy versions)
# ==================================================
def dummy_mass_extractor(result_path: Path) -> dict:
"""
Dummy mass extractor.
In real use, would call: from optimization_engine.result_extractors.extractors import mass_extractor
"""
import random
# Simulate varying mass based on a simple model
# In reality, this would extract from OP2
base_mass = 0.45 # kg
variation = random.uniform(-0.05, 0.05)
return {
'total_mass': base_mass + variation,
'cg_x': 0.0,
'cg_y': 0.0,
'cg_z': 0.0,
'units': 'kg'
}
def dummy_stress_extractor(result_path: Path) -> dict:
"""
Dummy stress extractor.
In real use, would call: from optimization_engine.result_extractors.extractors import stress_extractor
"""
import random
# Simulate stress results
base_stress = 180.0 # MPa
variation = random.uniform(-30.0, 30.0)
return {
'max_von_mises': base_stress + variation,
'stress_type': 'von_mises',
'element_id': 1234,
'units': 'MPa'
}
def dummy_displacement_extractor(result_path: Path) -> dict:
"""
Dummy displacement extractor.
In real use, would call: from optimization_engine.result_extractors.extractors import displacement_extractor
"""
import random
# Simulate displacement results
base_disp = 0.9 # mm
variation = random.uniform(-0.2, 0.2)
return {
'max_displacement': base_disp + variation,
'max_node_id': 5678,
'dx': 0.0,
'dy': 0.0,
'dz': base_disp + variation,
'units': 'mm'
}
# ==================================================
# MAIN: Run optimization
# ==================================================
if __name__ == "__main__":
print("="*60)
print("ATOMIZER - OPTIMIZATION EXAMPLE")
print("="*60)
# Path to optimization configuration
config_path = project_root / "examples/bracket/optimization_config.json"
if not config_path.exists():
print(f"Error: Configuration file not found: {config_path}")
print("Please run the MCP build_optimization_config tool first.")
sys.exit(1)
print(f"\nConfiguration: {config_path}")
# Create result extractors dict
extractors = {
'mass_extractor': dummy_mass_extractor,
'stress_extractor': dummy_stress_extractor,
'displacement_extractor': dummy_displacement_extractor
}
# Create optimization runner
runner = OptimizationRunner(
config_path=config_path,
model_updater=bracket_model_updater,
simulation_runner=bracket_simulation_runner,
result_extractors=extractors
)
# Run optimization (use fewer trials for demo)
print("\n" + "="*60)
print("Starting optimization with 10 trials (demo)")
print("For full optimization, modify n_trials in config")
print("="*60)
# Override n_trials for demo
runner.config['optimization_settings']['n_trials'] = 10
# Run!
study = runner.run(study_name="bracket_optimization_demo")
print("\n" + "="*60)
print("OPTIMIZATION RESULTS")
print("="*60)
print(f"\nBest parameters found:")
for param, value in study.best_params.items():
print(f" {param}: {value:.4f}")
print(f"\nBest objective value: {study.best_value:.6f}")
print(f"\nResults saved to: {runner.output_dir}")
print(" - history.csv (all trials)")
print(" - history.json (detailed results)")
print(" - optimization_summary.json (best results)")
print("\n" + "="*60)
print("NEXT STEPS:")
print("="*60)
print("1. Install pyNastran: conda install -c conda-forge pynastran")
print("2. Replace dummy extractors with real OP2 extractors")
print("3. Integrate with NX solver (batch mode or NXOpen)")
print("4. Run full optimization with n_trials=100+")
print("="*60)