feat: Add substudy system with live history tracking and workflow fixes
Major Features: - Hierarchical substudy system (like NX Solutions/Subcases) * Shared model files across all substudies * Independent configuration per substudy * Continuation support from previous substudies * Real-time incremental history updates - Live history tracking with optimization_history_incremental.json - Complete bracket_displacement_maximizing study with substudy examples Core Fixes: - Fixed expression update workflow to pass design_vars through simulation_runner * Restored working NX journal expression update mechanism * OP2 timestamp verification instead of file deletion * Resolved issue where all trials returned identical objective values - Fixed LLMOptimizationRunner to pass design variables to simulation runner - Enhanced NXSolver with timestamp-based file regeneration verification New Components: - optimization_engine/llm_optimization_runner.py - LLM-driven optimization runner - optimization_engine/optimization_setup_wizard.py - Phase 3.3 setup wizard - studies/bracket_displacement_maximizing/ - Complete substudy example * run_substudy.py - Substudy runner with continuation * run_optimization.py - Standalone optimization runner * config/substudy_template.json - Template for new substudies * substudies/coarse_exploration/ - 20-trial coarse search * substudies/fine_tuning/ - 50-trial refinement (continuation example) * SUBSTUDIES_README.md - Complete substudy documentation Technical Improvements: - Incremental history saving after each trial (optimization_history_incremental.json) - Expression update workflow: .prt update → NX journal receives values → geometry update → FEM update → solve - Trial indexing fix in substudy result saving - Updated README with substudy system documentation Testing: - Successfully ran 20-trial coarse_exploration substudy - Verified different objective values across trials (workflow fix validated) - Confirmed live history updates in real-time - Tested shared model file usage across substudies 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
BIN
tests/Bracket.prt.bak_20251116_200950
Normal file
BIN
tests/Bracket.prt.bak_20251116_200950
Normal file
Binary file not shown.
BIN
tests/Bracket.prt.bak_20251116_201643
Normal file
BIN
tests/Bracket.prt.bak_20251116_201643
Normal file
Binary file not shown.
BIN
tests/Bracket.prt.bak_20251116_201649
Normal file
BIN
tests/Bracket.prt.bak_20251116_201649
Normal file
Binary file not shown.
BIN
tests/Bracket.prt.bak_20251116_201654
Normal file
BIN
tests/Bracket.prt.bak_20251116_201654
Normal file
Binary file not shown.
BIN
tests/Bracket.prt.bak_20251116_201659
Normal file
BIN
tests/Bracket.prt.bak_20251116_201659
Normal file
Binary file not shown.
BIN
tests/Bracket.prt.bak_20251116_201705
Normal file
BIN
tests/Bracket.prt.bak_20251116_201705
Normal file
Binary file not shown.
BIN
tests/Bracket.prt.bak_20251116_201736
Normal file
BIN
tests/Bracket.prt.bak_20251116_201736
Normal file
Binary file not shown.
BIN
tests/Bracket.prt.bak_20251116_201741
Normal file
BIN
tests/Bracket.prt.bak_20251116_201741
Normal file
Binary file not shown.
BIN
tests/Bracket.prt.bak_20251116_201806
Normal file
BIN
tests/Bracket.prt.bak_20251116_201806
Normal file
Binary file not shown.
BIN
tests/Bracket.prt.bak_20251116_201811
Normal file
BIN
tests/Bracket.prt.bak_20251116_201811
Normal file
Binary file not shown.
BIN
tests/Bracket.prt.bak_20251116_201816
Normal file
BIN
tests/Bracket.prt.bak_20251116_201816
Normal file
Binary file not shown.
BIN
tests/Bracket.prt.bak_20251116_201821
Normal file
BIN
tests/Bracket.prt.bak_20251116_201821
Normal file
Binary file not shown.
BIN
tests/Bracket.prt.bak_20251116_203748
Normal file
BIN
tests/Bracket.prt.bak_20251116_203748
Normal file
Binary file not shown.
BIN
tests/Bracket.prt.bak_20251116_203753
Normal file
BIN
tests/Bracket.prt.bak_20251116_203753
Normal file
Binary file not shown.
BIN
tests/Bracket.prt.bak_20251116_203758
Normal file
BIN
tests/Bracket.prt.bak_20251116_203758
Normal file
Binary file not shown.
BIN
tests/Bracket.prt.bak_20251116_203803
Normal file
BIN
tests/Bracket.prt.bak_20251116_203803
Normal file
Binary file not shown.
BIN
tests/Bracket.prt.bak_20251116_203824
Normal file
BIN
tests/Bracket.prt.bak_20251116_203824
Normal file
Binary file not shown.
BIN
tests/Bracket.prt.bak_20251116_203829
Normal file
BIN
tests/Bracket.prt.bak_20251116_203829
Normal file
Binary file not shown.
BIN
tests/Bracket.prt.bak_20251116_204358
Normal file
BIN
tests/Bracket.prt.bak_20251116_204358
Normal file
Binary file not shown.
BIN
tests/Bracket.prt.bak_20251116_204403
Normal file
BIN
tests/Bracket.prt.bak_20251116_204403
Normal file
Binary file not shown.
BIN
tests/Bracket.prt.bak_20251116_204408
Normal file
BIN
tests/Bracket.prt.bak_20251116_204408
Normal file
Binary file not shown.
BIN
tests/Bracket.prt.bak_20251116_204413
Normal file
BIN
tests/Bracket.prt.bak_20251116_204413
Normal file
Binary file not shown.
BIN
tests/Bracket.prt.bak_20251116_204418
Normal file
BIN
tests/Bracket.prt.bak_20251116_204418
Normal file
Binary file not shown.
BIN
tests/Bracket.prt.bak_20251116_204424
Normal file
BIN
tests/Bracket.prt.bak_20251116_204424
Normal file
Binary file not shown.
BIN
tests/Bracket_fem1_i.prt
Normal file
BIN
tests/Bracket_fem1_i.prt
Normal file
Binary file not shown.
Binary file not shown.
@@ -3,15 +3,15 @@ $*
|
||||
$* Simcenter v2412.0.0.3001 Translator
|
||||
$* for Simcenter Nastran version 2412.0
|
||||
$*
|
||||
$* FEM FILE: C:\Users\antoi\Documents\Atomaste\Atomizer\tests\Bracket_fem1.fem
|
||||
$* SIM FILE: C:\Users\antoi\Documents\Atomaste\Atomizer\tests\Bracket_sim1.sim
|
||||
$* FEM FILE: c:\Users\antoi\Documents\Atomaste\Atomizer\tests\Bracket_fem1.fem
|
||||
$* SIM FILE: c:\Users\antoi\Documents\Atomaste\Atomizer\tests\Bracket_sim1.sim
|
||||
$* ANALYSIS TYPE: Structural
|
||||
$* SOLUTION NAME: Solution 1
|
||||
$* SOLUTION TYPE: SOL 101 Linear Statics
|
||||
$*
|
||||
$* SOLVER INPUT FILE: bracket_sim1-solution_1.dat
|
||||
$* CREATION DATE: 15-Nov-2025
|
||||
$* CREATION TIME: 12:17:41
|
||||
$* CREATION DATE: 16-Nov-2025
|
||||
$* CREATION TIME: 20:44:27
|
||||
$* HOSTNAME: AntoineThinkpad
|
||||
$* NASTRAN LICENSE: Desktop Bundle
|
||||
$*
|
||||
|
||||
33
tests/bracket_sim1-solution_1.t13980_32.asg
Normal file
33
tests/bracket_sim1-solution_1.t13980_32.asg
Normal file
@@ -0,0 +1,33 @@
|
||||
Nastran BUFFSIZE=32769 $(c:/program files/siemens/simcenter3d_2412/nxnastran/conf/nastran.rcf[1])
|
||||
Nastran BUFFPOOL=20.0X $(c:/program files/siemens/simcenter3d_2412/nxnastran/conf/nastran.rcf[4])
|
||||
Nastran DIAGA=128 DIAGB=0 $(c:/program files/siemens/simcenter3d_2412/nxnastran/conf/nastran.rcf[7])
|
||||
Nastran SYSTEM(442)=2 $(command line[5])
|
||||
Nastran REAL=8545370112 $(Memory limit for MPI and other specialized modules)
|
||||
JID='c:\Users\antoi\Documents\Atomaste\Atomizer\tests\bracket_sim1-solution_1.dat'
|
||||
OUT='./bracket_sim1-solution_1'
|
||||
MEM=3846123520
|
||||
MACH='Intel64 Family 6 Model 183 Stepping 1'
|
||||
OPER='Windows 10'
|
||||
OSV=' '
|
||||
MODEL='Intel(R) Core(TM) i7-14700HX (AntoineThinkpad)'
|
||||
CONFIG=8666
|
||||
NPROC=28
|
||||
symbol=DELDIR='c:/program files/siemens/simcenter3d_2412/nxnastran/scnas/nast/del' $(program default)
|
||||
symbol=DEMODIR='c:/program files/siemens/simcenter3d_2412/nxnastran/scnas/nast/demo' $(program default)
|
||||
symbol=SSSALTERDIR='c:/program files/siemens/simcenter3d_2412/nxnastran/scnas/nast/misc/sssalter' $(program default)
|
||||
symbol=TPLDIR='c:/program files/siemens/simcenter3d_2412/nxnastran/scnas/nast/tpl' $(program default)
|
||||
SDIR='c:/users/antoi/appdata/local/temp/bracket_sim1-solution_1.T13980_32'
|
||||
DBS='c:/users/antoi/appdata/local/temp/bracket_sim1-solution_1.T13980_32'
|
||||
SCR=yes
|
||||
SMEM=20.0X
|
||||
NEWDEL='c:/program files/siemens/simcenter3d_2412/nxnastran/scnas/em64tntl/SSS'
|
||||
DEL='NXNDEF'
|
||||
AUTH='29000@AntoineThinkpad'
|
||||
AUTHQUE=0
|
||||
MSGCAT='c:/program files/siemens/simcenter3d_2412/nxnastran/scnas/em64tntl/analysis.msg'
|
||||
MSGDEST='f06'
|
||||
PROG=bundle
|
||||
NEWS='c:/program files/siemens/simcenter3d_2412/nxnastran/scnas/nast/news.txt'
|
||||
UMATLIB='libnxumat.dll'
|
||||
UCRPLIB='libucreep.dll'
|
||||
USOLLIB='libusol.dll'
|
||||
324
tests/interactive_optimization_setup.py
Normal file
324
tests/interactive_optimization_setup.py
Normal file
@@ -0,0 +1,324 @@
|
||||
"""
|
||||
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.optimization_setup_wizard import OptimizationSetupWizard
|
||||
from optimization_engine.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()
|
||||
278
tests/test_bracket_full_optimization.py
Normal file
278
tests/test_bracket_full_optimization.py
Normal file
@@ -0,0 +1,278 @@
|
||||
"""
|
||||
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))
|
||||
|
||||
from optimization_engine.llm_optimization_runner import LLMOptimizationRunner
|
||||
from optimization_engine.nx_solver import NXSolver
|
||||
from optimization_engine.nx_updater import NXParameterUpdater
|
||||
|
||||
# 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)
|
||||
144
tests/test_bracket_llm_runner.py
Normal file
144
tests/test_bracket_llm_runner.py
Normal file
@@ -0,0 +1,144 @@
|
||||
"""
|
||||
Test LLM Optimization Runner with Bracket Problem
|
||||
|
||||
Objective: Maximize displacement
|
||||
Constraint: Stress must remain under safety factor of 4
|
||||
|
||||
Assuming Aluminum 6061-T6:
|
||||
- Yield Strength: 276 MPa
|
||||
- Safety Factor: 4
|
||||
- Allowable Stress: 276 / 4 = 69 MPa
|
||||
|
||||
This test validates Phase 3.2 LLM-enhanced optimization with stress constraints.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from optimization_engine.llm_optimization_runner import LLMOptimizationRunner
|
||||
|
||||
# LLM workflow for bracket optimization
|
||||
# Goal: Maximize displacement while keeping stress below safety factor
|
||||
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': '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': [
|
||||
{
|
||||
'action': 'weighted_objective',
|
||||
'params': {
|
||||
'inputs': ['neg_displacement'],
|
||||
'weights': [1.0],
|
||||
'objective': 'minimize'
|
||||
},
|
||||
'description': 'Minimize negative displacement (maximize actual displacement)'
|
||||
}
|
||||
],
|
||||
'optimization': {
|
||||
'algorithm': 'TPE',
|
||||
'direction': 'minimize', # Minimize negative displacement = maximize displacement
|
||||
'design_variables': [
|
||||
{
|
||||
'parameter': 'wall_thickness',
|
||||
'min': 3.0,
|
||||
'max': 8.0,
|
||||
'units': 'mm'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def dummy_updater(design_vars):
|
||||
"""Dummy model updater for testing."""
|
||||
print(f" [Test] Would update model with: {design_vars}")
|
||||
|
||||
|
||||
def dummy_runner():
|
||||
"""Dummy simulation runner for testing."""
|
||||
print(" [Test] Would run NX simulation...")
|
||||
# Return path to existing test OP2
|
||||
return Path('tests/bracket_sim1-solution_1.op2')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("=" * 80)
|
||||
print("Test: LLM Runner with Bracket Problem")
|
||||
print("=" * 80)
|
||||
print()
|
||||
print("Problem Definition:")
|
||||
print(" Objective: MAXIMIZE displacement")
|
||||
print(" Constraint: Stress must remain below yield/SF")
|
||||
print(" Material: Aluminum 6061-T6 (Yield = 276 MPa)")
|
||||
print(" Safety Factor: 4.0")
|
||||
print(" Allowable Stress: 69 MPa")
|
||||
print()
|
||||
print("LLM Workflow:")
|
||||
print(f" Engineering features: {len(llm_workflow['engineering_features'])}")
|
||||
print(f" Inline calculations: {len(llm_workflow['inline_calculations'])}")
|
||||
print(f" Post-processing hooks: {len(llm_workflow['post_processing_hooks'])}")
|
||||
print(f" Design variables: {len(llm_workflow['optimization']['design_variables'])}")
|
||||
print()
|
||||
|
||||
# Initialize LLM runner
|
||||
print("Initializing LLM optimization runner...")
|
||||
try:
|
||||
runner = LLMOptimizationRunner(
|
||||
llm_workflow=llm_workflow,
|
||||
model_updater=dummy_updater,
|
||||
simulation_runner=dummy_runner,
|
||||
study_name='bracket_maximize_disp_constrained'
|
||||
)
|
||||
|
||||
print()
|
||||
print("=" * 80)
|
||||
print("Runner Initialized Successfully!")
|
||||
print("=" * 80)
|
||||
print(f" Extractors generated: {len(runner.extractors)}")
|
||||
print(f" Inline calculations: {len(runner.inline_code)}")
|
||||
hook_summary = runner.hook_manager.get_summary()
|
||||
print(f" Hooks loaded: {hook_summary['enabled_hooks']}")
|
||||
print()
|
||||
|
||||
print("To run full optimization, call runner.run_optimization(n_trials=50)")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\nERROR during initialization: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
70
tests/test_llm_runner_init.py
Normal file
70
tests/test_llm_runner_init.py
Normal file
@@ -0,0 +1,70 @@
|
||||
"""Quick test of LLM runner initialization"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from optimization_engine.llm_optimization_runner import LLMOptimizationRunner
|
||||
|
||||
# Example LLM workflow
|
||||
llm_workflow = {
|
||||
'engineering_features': [
|
||||
{
|
||||
'action': 'extract_displacement',
|
||||
'domain': 'result_extraction',
|
||||
'description': 'Extract displacement from OP2',
|
||||
'params': {'result_type': 'displacement'}
|
||||
}
|
||||
],
|
||||
'inline_calculations': [
|
||||
{
|
||||
'action': 'normalize',
|
||||
'params': {
|
||||
'input': 'max_displacement',
|
||||
'reference': 'max_allowed_disp',
|
||||
'value': 5.0
|
||||
},
|
||||
'code_hint': 'norm_disp = max_displacement / 5.0'
|
||||
}
|
||||
],
|
||||
'post_processing_hooks': [
|
||||
{
|
||||
'action': 'weighted_objective',
|
||||
'params': {
|
||||
'inputs': ['norm_disp'],
|
||||
'weights': [1.0],
|
||||
'objective': 'minimize'
|
||||
}
|
||||
}
|
||||
],
|
||||
'optimization': {
|
||||
'algorithm': 'TPE',
|
||||
'direction': 'minimize',
|
||||
'design_variables': [
|
||||
{
|
||||
'parameter': 'wall_thickness',
|
||||
'min': 3.0,
|
||||
'max': 8.0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
def dummy_updater(dv):
|
||||
pass
|
||||
|
||||
def dummy_runner():
|
||||
return Path('tests/bracket_sim1-solution_1.op2')
|
||||
|
||||
print("Initializing LLM runner...")
|
||||
runner = LLMOptimizationRunner(
|
||||
llm_workflow=llm_workflow,
|
||||
model_updater=dummy_updater,
|
||||
simulation_runner=dummy_runner,
|
||||
study_name='test'
|
||||
)
|
||||
|
||||
print(f"\nSuccess!")
|
||||
print(f"Extractors: {len(runner.extractors)}")
|
||||
print(f"Inline code: {len(runner.inline_code)}")
|
||||
print(f"Hooks: {runner.hook_manager.get_summary()['enabled_hooks']}")
|
||||
168
tests/test_optimization_setup_wizard.py
Normal file
168
tests/test_optimization_setup_wizard.py
Normal file
@@ -0,0 +1,168 @@
|
||||
"""
|
||||
Test Optimization Setup Wizard - Phase 3.3
|
||||
|
||||
This test demonstrates the complete validation workflow:
|
||||
1. Introspect model for expressions
|
||||
2. Run baseline simulation
|
||||
3. Introspect OP2 for element types
|
||||
4. Validate complete pipeline
|
||||
5. Report readiness for optimization
|
||||
|
||||
This prevents wasted time on optimizations that will fail!
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from optimization_engine.optimization_setup_wizard import OptimizationSetupWizard
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("=" * 80)
|
||||
print("Phase 3.3: Optimization Setup Wizard Test")
|
||||
print("=" * 80)
|
||||
print()
|
||||
print("This wizard validates the optimization pipeline BEFORE running trials.")
|
||||
print("It will:")
|
||||
print(" 1. Introspect NX model for available expressions")
|
||||
print(" 2. Run baseline simulation to generate OP2")
|
||||
print(" 3. Introspect OP2 to detect element types and results")
|
||||
print(" 4. Validate extraction/calculation/hook pipeline")
|
||||
print(" 5. Report readiness for optimization")
|
||||
print()
|
||||
|
||||
# Configuration
|
||||
prt_file = Path("tests/Bracket.prt")
|
||||
sim_file = Path("tests/Bracket_sim1.sim")
|
||||
|
||||
if not prt_file.exists():
|
||||
print(f"ERROR: Part file not found: {prt_file}")
|
||||
sys.exit(1)
|
||||
|
||||
if not sim_file.exists():
|
||||
print(f"ERROR: Simulation file not found: {sim_file}")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Part file: {prt_file}")
|
||||
print(f"Sim file: {sim_file}")
|
||||
print()
|
||||
|
||||
try:
|
||||
# Initialize wizard
|
||||
print("Initializing Optimization Setup Wizard...")
|
||||
wizard = OptimizationSetupWizard(prt_file, sim_file)
|
||||
print(" Wizard initialized!")
|
||||
print()
|
||||
|
||||
# Define optimization goal
|
||||
user_goal = "Maximize displacement while keeping stress below yield/4 (safety factor >= 4.0)"
|
||||
|
||||
# Custom workflow with safety factor constraint
|
||||
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' # Will be auto-detected
|
||||
}
|
||||
}
|
||||
],
|
||||
'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
|
||||
}
|
||||
|
||||
# Run complete validation
|
||||
print("=" * 80)
|
||||
print("RUNNING COMPLETE VALIDATION WORKFLOW")
|
||||
print("=" * 80)
|
||||
print()
|
||||
|
||||
success, validation_results = wizard.run_complete_validation(
|
||||
user_goal=user_goal,
|
||||
llm_workflow=llm_workflow
|
||||
)
|
||||
|
||||
print()
|
||||
print("=" * 80)
|
||||
print("VALIDATION COMPLETE")
|
||||
print("=" * 80)
|
||||
print()
|
||||
|
||||
if success:
|
||||
print("[SUCCESS] ALL VALIDATIONS PASSED!")
|
||||
print()
|
||||
print("Pipeline is ready for optimization. Key findings:")
|
||||
print()
|
||||
|
||||
# Show model expressions
|
||||
if wizard.model_info:
|
||||
print(f"Model Expressions ({len(wizard.model_info.expressions)}):")
|
||||
for name, info in wizard.model_info.expressions.items():
|
||||
print(f" - {name}: {info['value']} {info['units']}")
|
||||
print()
|
||||
|
||||
# Show OP2 contents
|
||||
if wizard.op2_info:
|
||||
print(f"OP2 Contents:")
|
||||
print(f" - Element types: {', '.join(wizard.op2_info.element_types)}")
|
||||
print(f" - Result types: {', '.join(wizard.op2_info.result_types)}")
|
||||
print(f" - Subcases: {wizard.op2_info.subcases}")
|
||||
print(f" - Nodes: {wizard.op2_info.node_count}")
|
||||
print(f" - Elements: {wizard.op2_info.element_count}")
|
||||
print()
|
||||
|
||||
# Show validation summary
|
||||
print(f"Validation Summary:")
|
||||
for result in validation_results:
|
||||
if result.success:
|
||||
print(f" {result.message}")
|
||||
print()
|
||||
|
||||
print("[OK] You can now start the optimization with confidence!")
|
||||
print(" The system has verified that all pipeline components work correctly.")
|
||||
|
||||
else:
|
||||
print("[FAILED] VALIDATION FAILED!")
|
||||
print()
|
||||
print("Issues found:")
|
||||
for result in validation_results:
|
||||
if not result.success:
|
||||
print(f" [ERROR] {result.message}")
|
||||
print()
|
||||
print("Please fix these issues before starting optimization.")
|
||||
sys.exit(1)
|
||||
|
||||
except Exception as e:
|
||||
print(f"\nERROR during validation: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
70
tests/test_timestamp_verification.py
Normal file
70
tests/test_timestamp_verification.py
Normal file
@@ -0,0 +1,70 @@
|
||||
"""
|
||||
Test: Verify NX regenerates .dat and .op2 files with new timestamps
|
||||
|
||||
This test verifies the fix for OP2 caching by checking file timestamps.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import time
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from optimization_engine.nx_solver import NXSolver
|
||||
|
||||
print("=" * 60)
|
||||
print("Testing timestamp verification fix")
|
||||
print("=" * 60)
|
||||
|
||||
sim_file = Path('studies/bracket_displacement_maximizing/model/Bracket_sim1.sim')
|
||||
|
||||
if not sim_file.exists():
|
||||
print(f"ERROR: Simulation file not found: {sim_file}")
|
||||
sys.exit(1)
|
||||
|
||||
# Check current timestamps
|
||||
dat_file = sim_file.parent / "bracket_sim1-solution_1.dat"
|
||||
op2_file = sim_file.parent / "bracket_sim1-solution_1.op2"
|
||||
|
||||
print(f"\nSimulation file: {sim_file}")
|
||||
print(f"Working directory: {sim_file.parent}")
|
||||
|
||||
if dat_file.exists():
|
||||
old_dat_time = dat_file.stat().st_mtime
|
||||
print(f"\nBefore solve:")
|
||||
print(f" .dat modified: {time.ctime(old_dat_time)}")
|
||||
|
||||
if op2_file.exists():
|
||||
old_op2_time = op2_file.stat().st_mtime
|
||||
print(f" .op2 modified: {time.ctime(old_op2_time)}")
|
||||
|
||||
# Run solve with timestamp verification
|
||||
print(f"\nRunning solve with timestamp verification...")
|
||||
solver = NXSolver(nastran_version='2412', use_journal=True)
|
||||
result = solver.run_simulation(sim_file)
|
||||
|
||||
print(f"\nSolve result: {'SUCCESS' if result['success'] else 'FAILED'}")
|
||||
|
||||
# Check new timestamps
|
||||
if op2_file.exists():
|
||||
new_op2_time = op2_file.stat().st_mtime
|
||||
print(f"\nAfter solve:")
|
||||
print(f" .op2 modified: {time.ctime(new_op2_time)}")
|
||||
|
||||
if 'old_op2_time' in locals():
|
||||
if new_op2_time > old_op2_time:
|
||||
print(f"\n ✓ SUCCESS! .op2 file was regenerated!")
|
||||
print(f" Time difference: {new_op2_time - old_op2_time:.1f} seconds")
|
||||
else:
|
||||
print(f"\n ✗ FAILED! .op2 file was NOT updated")
|
||||
print(f" This means NX did not regenerate the file")
|
||||
else:
|
||||
print(f"\n ✓ New .op2 file created")
|
||||
|
||||
if dat_file.exists():
|
||||
new_dat_time = dat_file.stat().st_mtime
|
||||
print(f" .dat modified: {time.ctime(new_dat_time)}")
|
||||
|
||||
if 'old_dat_time' in locals():
|
||||
if new_dat_time > old_dat_time:
|
||||
print(f"\n ✓ .dat file was also regenerated")
|
||||
Reference in New Issue
Block a user