""" 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)) 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 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()