2025-11-16 21:29:54 -05:00
|
|
|
"""
|
|
|
|
|
Interactive Optimization Setup - Phase 3.3
|
|
|
|
|
|
|
|
|
|
This script simulates a real Atomizer setup session where:
|
|
|
|
|
- User provides their optimization goal in natural language
|
|
|
|
|
- Atomizer LLM Assistant helps configure the optimization
|
|
|
|
|
- Wizard validates the setup before running
|
|
|
|
|
- User confirms and starts optimization
|
|
|
|
|
|
|
|
|
|
This is how Atomizer SHOULD work in production!
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import sys
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
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.config.setup_wizard import OptimizationSetupWizard
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
def print_section(title: str):
|
|
|
|
|
"""Print a section header."""
|
|
|
|
|
print()
|
|
|
|
|
print("=" * 80)
|
|
|
|
|
print(f" {title}")
|
|
|
|
|
print("=" * 80)
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def print_assistant(message: str):
|
|
|
|
|
"""Print Atomizer LLM Assistant message."""
|
|
|
|
|
print(f"\n[Atomizer] {message}\n")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def print_user(message: str):
|
|
|
|
|
"""Print user message."""
|
|
|
|
|
print(f"\n[User] {message}\n")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def print_wizard(message: str):
|
|
|
|
|
"""Print wizard message."""
|
|
|
|
|
print(f"[Wizard] {message}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
print_section("ATOMIZER - INTERACTIVE OPTIMIZATION SETUP")
|
|
|
|
|
|
|
|
|
|
print_assistant("Welcome to Atomizer! I'll help you set up your optimization.")
|
|
|
|
|
print_assistant("First, I need to know about your model files.")
|
|
|
|
|
|
|
|
|
|
# File paths
|
|
|
|
|
prt_file = Path("tests/Bracket.prt")
|
|
|
|
|
sim_file = Path("tests/Bracket_sim1.sim")
|
|
|
|
|
|
|
|
|
|
if not prt_file.exists() or not sim_file.exists():
|
|
|
|
|
print("[ERROR] Test files not found!")
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
print_user(f"I have a bracket model:")
|
|
|
|
|
print(f" - Part file: {prt_file}")
|
|
|
|
|
print(f" - Simulation file: {sim_file}")
|
|
|
|
|
|
|
|
|
|
print_assistant("Great! Let me initialize the Setup Wizard to analyze your model...")
|
|
|
|
|
|
|
|
|
|
# Initialize wizard
|
|
|
|
|
wizard = OptimizationSetupWizard(prt_file, sim_file)
|
|
|
|
|
|
|
|
|
|
print_section("STEP 1: MODEL INTROSPECTION")
|
|
|
|
|
|
|
|
|
|
print_assistant("I'm reading your NX model to find available design parameters...")
|
|
|
|
|
|
|
|
|
|
# Introspect model
|
|
|
|
|
model_info = wizard.introspect_model()
|
|
|
|
|
|
|
|
|
|
print_assistant(f"Found {len(model_info.expressions)} expressions in your model:")
|
|
|
|
|
for name, info in model_info.expressions.items():
|
|
|
|
|
print(f" - {name}: {info['value']} {info['units']}")
|
|
|
|
|
|
|
|
|
|
print_assistant("Which parameters would you like to use as design variables?")
|
|
|
|
|
|
|
|
|
|
print_user("I want to optimize tip_thickness and support_angle")
|
|
|
|
|
|
|
|
|
|
print_assistant("Perfect! Now, what's your optimization goal?")
|
|
|
|
|
|
|
|
|
|
print_user("I want to maximize displacement while keeping stress below")
|
|
|
|
|
print(" a safety factor of 4. The material is Aluminum 6061-T6.")
|
|
|
|
|
|
|
|
|
|
user_goal = "Maximize displacement while keeping stress below safety factor of 4 (Aluminum 6061-T6, yield=276 MPa)"
|
|
|
|
|
|
|
|
|
|
print_section("STEP 2: BASELINE SIMULATION")
|
|
|
|
|
|
|
|
|
|
print_assistant("To validate your setup, I need to run ONE baseline simulation.")
|
|
|
|
|
print_assistant("This will generate an OP2 file that I can analyze to ensure")
|
|
|
|
|
print_assistant("the extraction pipeline will work correctly.")
|
|
|
|
|
print_assistant("")
|
|
|
|
|
print_assistant("Running baseline simulation with current parameter values...")
|
|
|
|
|
|
|
|
|
|
# Run baseline
|
|
|
|
|
baseline_op2 = wizard.run_baseline_simulation()
|
|
|
|
|
|
|
|
|
|
print_assistant(f"Baseline simulation complete! OP2 file: {baseline_op2.name}")
|
|
|
|
|
|
|
|
|
|
print_section("STEP 3: OP2 INTROSPECTION")
|
|
|
|
|
|
|
|
|
|
print_assistant("Now I'll analyze the OP2 file to see what's actually in there...")
|
|
|
|
|
|
|
|
|
|
# Introspect OP2
|
|
|
|
|
op2_info = wizard.introspect_op2()
|
|
|
|
|
|
|
|
|
|
print_assistant("Here's what I found in your OP2 file:")
|
|
|
|
|
print(f" - Element types with stress: {', '.join(op2_info.element_types)}")
|
|
|
|
|
print(f" - Available results: {', '.join(op2_info.result_types)}")
|
|
|
|
|
print(f" - Number of elements: {op2_info.element_count}")
|
|
|
|
|
print(f" - Number of nodes: {op2_info.node_count}")
|
|
|
|
|
|
|
|
|
|
print_section("STEP 4: LLM-GUIDED CONFIGURATION")
|
|
|
|
|
|
|
|
|
|
print_assistant("Based on your goal and the OP2 contents, here's what I recommend:")
|
|
|
|
|
print_assistant("")
|
|
|
|
|
print_assistant("OBJECTIVE:")
|
|
|
|
|
print_assistant(" - Maximize displacement (minimize negative displacement)")
|
|
|
|
|
print_assistant("")
|
|
|
|
|
print_assistant("EXTRACTIONS:")
|
|
|
|
|
print_assistant(" - Extract displacement from OP2")
|
|
|
|
|
print_assistant(f" - Extract stress from {op2_info.element_types[0]} elements")
|
|
|
|
|
print_assistant(" (I detected these element types in your model)")
|
|
|
|
|
print_assistant("")
|
|
|
|
|
print_assistant("CALCULATIONS:")
|
|
|
|
|
print_assistant(" - Calculate safety factor: SF = 276 MPa / max_stress")
|
|
|
|
|
print_assistant(" - Negate displacement for minimization")
|
|
|
|
|
print_assistant("")
|
|
|
|
|
print_assistant("CONSTRAINT:")
|
|
|
|
|
print_assistant(" - Enforce SF >= 4.0 with penalty")
|
|
|
|
|
print_assistant("")
|
|
|
|
|
print_assistant("DESIGN VARIABLES:")
|
|
|
|
|
print_assistant(f" - tip_thickness: {model_info.expressions['tip_thickness']['value']} mm (suggest range: 15-25 mm)")
|
|
|
|
|
print_assistant(f" - support_angle: {model_info.expressions['support_angle']['value']} degrees (suggest range: 20-40 deg)")
|
|
|
|
|
|
|
|
|
|
print_user("That looks good! Let's use those ranges.")
|
|
|
|
|
|
|
|
|
|
# Build configuration
|
|
|
|
|
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': f'Extract von Mises stress from {op2_info.element_types[0]} elements',
|
|
|
|
|
'params': {
|
|
|
|
|
'result_type': 'stress',
|
|
|
|
|
'element_type': op2_info.element_types[0].lower() # Use detected element type!
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
'inline_calculations': [
|
|
|
|
|
{
|
|
|
|
|
'action': 'calculate_safety_factor',
|
|
|
|
|
'params': {
|
|
|
|
|
'input': 'max_von_mises',
|
|
|
|
|
'yield_strength': 276.0,
|
|
|
|
|
'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',
|
|
|
|
|
'design_variables': [
|
|
|
|
|
{
|
|
|
|
|
'parameter': 'tip_thickness',
|
|
|
|
|
'min': 15.0,
|
|
|
|
|
'max': 25.0,
|
|
|
|
|
'units': 'mm'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'parameter': 'support_angle',
|
|
|
|
|
'min': 20.0,
|
|
|
|
|
'max': 40.0,
|
|
|
|
|
'units': 'degrees'
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
print_section("STEP 5: PIPELINE VALIDATION (DRY RUN)")
|
|
|
|
|
|
|
|
|
|
print_assistant("Before running 20-30 optimization trials, let me validate that")
|
|
|
|
|
print_assistant("EVERYTHING works correctly with your baseline OP2 file...")
|
|
|
|
|
print_assistant("")
|
|
|
|
|
print_assistant("Running dry-run validation...")
|
|
|
|
|
|
|
|
|
|
# Validate pipeline
|
|
|
|
|
validation_results = wizard.validate_pipeline(llm_workflow)
|
|
|
|
|
|
|
|
|
|
# Check results
|
|
|
|
|
all_passed = all(r.success for r in validation_results)
|
|
|
|
|
|
|
|
|
|
print()
|
|
|
|
|
print_wizard("VALIDATION RESULTS:")
|
|
|
|
|
for result in validation_results:
|
|
|
|
|
status = "[OK]" if result.success else "[FAIL]"
|
|
|
|
|
print_wizard(f" {status} {result.component}: {result.message.split(':')[-1].strip()}")
|
|
|
|
|
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
if all_passed:
|
|
|
|
|
print_section("VALIDATION PASSED!")
|
|
|
|
|
|
|
|
|
|
print_assistant("Excellent! All pipeline components validated successfully.")
|
|
|
|
|
print_assistant("Your optimization is ready to run!")
|
|
|
|
|
print_assistant("")
|
|
|
|
|
print_assistant("Summary:")
|
|
|
|
|
print(f" - Design variables: tip_thickness (15-25mm), support_angle (20-40deg)")
|
|
|
|
|
print(f" - Objective: Maximize displacement")
|
|
|
|
|
print(f" - Constraint: Safety factor >= 4.0")
|
|
|
|
|
print(f" - Material: Aluminum 6061-T6 (Yield = 276 MPa)")
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
print_user("Great! Let's run 10 trials to test it.")
|
|
|
|
|
|
|
|
|
|
print_section("STEP 6: RUNNING OPTIMIZATION")
|
|
|
|
|
|
|
|
|
|
print_assistant("Initializing optimization runner...")
|
|
|
|
|
|
|
|
|
|
# Create updater and runner
|
|
|
|
|
def create_model_updater(prt_file: Path):
|
|
|
|
|
updater = NXParameterUpdater(prt_file_path=prt_file)
|
|
|
|
|
def update_model(design_vars: dict):
|
|
|
|
|
updater.update_expressions(design_vars)
|
|
|
|
|
updater.save()
|
|
|
|
|
return update_model
|
|
|
|
|
|
|
|
|
|
def create_simulation_runner(sim_file: Path):
|
|
|
|
|
solver = NXSolver(nastran_version='2412', use_journal=True)
|
|
|
|
|
def run_simulation() -> Path:
|
|
|
|
|
result = solver.run_simulation(sim_file)
|
|
|
|
|
return result['op2_file']
|
|
|
|
|
return run_simulation
|
|
|
|
|
|
|
|
|
|
model_updater = create_model_updater(prt_file)
|
|
|
|
|
simulation_runner = create_simulation_runner(sim_file)
|
|
|
|
|
|
|
|
|
|
# Initialize runner
|
|
|
|
|
runner = LLMOptimizationRunner(
|
|
|
|
|
llm_workflow=llm_workflow,
|
|
|
|
|
model_updater=model_updater,
|
|
|
|
|
simulation_runner=simulation_runner,
|
|
|
|
|
study_name='bracket_interactive_setup'
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
print_assistant("Starting optimization with 10 trials...")
|
|
|
|
|
print_assistant("(This will take a few minutes...)")
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
# Run optimization
|
|
|
|
|
results = runner.run_optimization(n_trials=10)
|
|
|
|
|
|
|
|
|
|
print()
|
|
|
|
|
print_section("OPTIMIZATION COMPLETE!")
|
|
|
|
|
|
|
|
|
|
print_assistant("Optimization finished! Here are the results:")
|
|
|
|
|
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()
|
|
|
|
|
print(f" Results saved to: {runner.output_dir}")
|
|
|
|
|
|
|
|
|
|
# Show some trial history
|
|
|
|
|
print()
|
|
|
|
|
print_assistant("Trial history (last 5 trials):")
|
|
|
|
|
print()
|
|
|
|
|
print(" Trial | Tip(mm) | Angle(°) | Disp(mm) | Stress(MPa) | Objective")
|
|
|
|
|
print(" ------|---------|----------|----------|-------------|----------")
|
|
|
|
|
|
|
|
|
|
for trial in results['history'][-5:]:
|
|
|
|
|
trial_num = trial['trial_number']
|
|
|
|
|
tip = trial['design_variables']['tip_thickness']
|
|
|
|
|
ang = trial['design_variables']['support_angle']
|
|
|
|
|
disp = trial['results'].get('max_displacement', 0)
|
|
|
|
|
stress = trial['results'].get('max_von_mises', 0)
|
|
|
|
|
obj = trial['objective']
|
|
|
|
|
print(f" {trial_num:5d} | {tip:7.2f} | {ang:8.2f} | {disp:8.5f} | {stress:11.2f} | {obj:9.6f}")
|
|
|
|
|
|
|
|
|
|
print()
|
|
|
|
|
print_assistant("Success! Your optimization completed without errors.")
|
|
|
|
|
print_assistant("This is exactly how Atomizer should work - validate first,")
|
|
|
|
|
print_assistant("then optimize with confidence!")
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
print_section("VALIDATION FAILED!")
|
|
|
|
|
|
|
|
|
|
print_assistant("The validation found issues that need to be fixed:")
|
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
|
for result in validation_results:
|
|
|
|
|
if not result.success:
|
|
|
|
|
print(f" [ERROR] {result.message}")
|
|
|
|
|
|
|
|
|
|
print()
|
|
|
|
|
print_assistant("Please fix these issues before starting the optimization.")
|
|
|
|
|
print_assistant("This saved you from wasting time on 20-30 failed trials!")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
main()
|