Implement study persistence and resumption capabilities for optimization workflows: Features: - Resume existing studies to add more trials - Create new studies when topology/config changes - Study metadata tracking (creation date, trials, config hash) - SQLite database persistence for Optuna studies - Configuration change detection with warnings - List all available studies Key Changes: - Enhanced OptimizationRunner.run() with resume parameter - Added _load_existing_study() for study resumption - Added _save_study_metadata() for tracking - Added _get_config_hash() to detect topology changes - Added list_studies() to view all studies - SQLite storage for study persistence Updated Files: - optimization_engine/runner.py: Core study management - examples/test_journal_optimization.py: Interactive study management - examples/study_management_example.py: Comprehensive examples Usage Examples: # New study runner.run(study_name="bracket_v1", n_trials=50) # Resume study (add 25 more trials) runner.run(study_name="bracket_v1", n_trials=25, resume=True) # New study after topology change runner.run(study_name="bracket_v2", n_trials=50) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
262 lines
7.9 KiB
Python
262 lines
7.9 KiB
Python
"""
|
|
Study Management Example
|
|
|
|
This script demonstrates how to use the study management features:
|
|
1. Create a new study
|
|
2. Resume an existing study to add more trials
|
|
3. List all available studies
|
|
4. Create a new study after topology/configuration changes
|
|
"""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# 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_solver import run_nx_simulation
|
|
from optimization_engine.result_extractors import (
|
|
extract_stress_from_op2,
|
|
extract_displacement_from_op2
|
|
)
|
|
|
|
|
|
def bracket_model_updater(design_vars: dict):
|
|
"""Update bracket model with new design variable values."""
|
|
from integration.nx_expression_updater import update_expressions_from_file
|
|
|
|
sim_file = Path('examples/bracket/Bracket_sim1.sim')
|
|
|
|
# Map design variables to NX expressions
|
|
expressions = {
|
|
'tip_thickness': design_vars['tip_thickness'],
|
|
'support_angle': design_vars['support_angle']
|
|
}
|
|
|
|
update_expressions_from_file(
|
|
sim_file=sim_file,
|
|
expressions=expressions
|
|
)
|
|
|
|
|
|
def bracket_simulation_runner() -> Path:
|
|
"""Run bracket simulation using journal-based NX solver."""
|
|
sim_file = Path('examples/bracket/Bracket_sim1.sim')
|
|
|
|
op2_file = run_nx_simulation(
|
|
sim_file=sim_file,
|
|
nastran_version='2412',
|
|
timeout=300,
|
|
cleanup=False,
|
|
use_journal=True
|
|
)
|
|
|
|
return op2_file
|
|
|
|
|
|
def stress_extractor(result_path: Path) -> dict:
|
|
"""Extract stress results from OP2."""
|
|
results = extract_stress_from_op2(result_path)
|
|
return results
|
|
|
|
|
|
def displacement_extractor(result_path: Path) -> dict:
|
|
"""Extract displacement results from OP2."""
|
|
results = extract_displacement_from_op2(result_path)
|
|
return results
|
|
|
|
|
|
def example_1_new_study():
|
|
"""
|
|
Example 1: Create a new optimization study with 20 trials
|
|
"""
|
|
print("\n" + "="*70)
|
|
print("EXAMPLE 1: Creating a New Study")
|
|
print("="*70)
|
|
|
|
config_path = Path('examples/bracket/optimization_config_stress_displacement.json')
|
|
|
|
runner = OptimizationRunner(
|
|
config_path=config_path,
|
|
model_updater=bracket_model_updater,
|
|
simulation_runner=bracket_simulation_runner,
|
|
result_extractors={
|
|
'stress_extractor': stress_extractor,
|
|
'displacement_extractor': displacement_extractor
|
|
}
|
|
)
|
|
|
|
# Create a new study with a specific name
|
|
# This uses the config's n_trials (50) unless overridden
|
|
study = runner.run(
|
|
study_name="bracket_optimization_v1",
|
|
n_trials=20, # Override to 20 trials for this example
|
|
resume=False # Create new study
|
|
)
|
|
|
|
print("\nStudy completed successfully!")
|
|
print(f"Database saved to: {runner._get_study_db_path('bracket_optimization_v1')}")
|
|
|
|
|
|
def example_2_resume_study():
|
|
"""
|
|
Example 2: Resume an existing study to add more trials
|
|
"""
|
|
print("\n" + "="*70)
|
|
print("EXAMPLE 2: Resuming an Existing Study")
|
|
print("="*70)
|
|
|
|
config_path = Path('examples/bracket/optimization_config_stress_displacement.json')
|
|
|
|
runner = OptimizationRunner(
|
|
config_path=config_path,
|
|
model_updater=bracket_model_updater,
|
|
simulation_runner=bracket_simulation_runner,
|
|
result_extractors={
|
|
'stress_extractor': stress_extractor,
|
|
'displacement_extractor': displacement_extractor
|
|
}
|
|
)
|
|
|
|
# Resume the study created in example 1
|
|
# Add 30 more trials (bringing total to 50)
|
|
study = runner.run(
|
|
study_name="bracket_optimization_v1",
|
|
n_trials=30, # Additional trials to run
|
|
resume=True # Resume existing study
|
|
)
|
|
|
|
print("\nStudy resumed and expanded successfully!")
|
|
print(f"Total trials: {len(study.trials)}")
|
|
|
|
|
|
def example_3_list_studies():
|
|
"""
|
|
Example 3: List all available studies
|
|
"""
|
|
print("\n" + "="*70)
|
|
print("EXAMPLE 3: Listing All Studies")
|
|
print("="*70)
|
|
|
|
config_path = Path('examples/bracket/optimization_config_stress_displacement.json')
|
|
|
|
runner = OptimizationRunner(
|
|
config_path=config_path,
|
|
model_updater=bracket_model_updater,
|
|
simulation_runner=bracket_simulation_runner,
|
|
result_extractors={
|
|
'stress_extractor': stress_extractor,
|
|
'displacement_extractor': displacement_extractor
|
|
}
|
|
)
|
|
|
|
studies = runner.list_studies()
|
|
|
|
if not studies:
|
|
print("No studies found.")
|
|
else:
|
|
print(f"\nFound {len(studies)} studies:\n")
|
|
for study in studies:
|
|
print(f"Study: {study['study_name']}")
|
|
print(f" Created: {study['created_at']}")
|
|
print(f" Total trials: {study.get('total_trials', 0)}")
|
|
print(f" Resume count: {study.get('resume_count', 0)}")
|
|
print(f" Config hash: {study.get('config_hash', 'N/A')[:8]}...")
|
|
print()
|
|
|
|
|
|
def example_4_new_study_after_change():
|
|
"""
|
|
Example 4: Create a new study after topology/configuration changes
|
|
|
|
This demonstrates what to do when:
|
|
- Geometry topology has changed significantly
|
|
- Design variables have been added/removed
|
|
- Objectives have changed
|
|
|
|
In these cases, the surrogate model from the previous study is no longer valid,
|
|
so you should create a NEW study rather than resume.
|
|
"""
|
|
print("\n" + "="*70)
|
|
print("EXAMPLE 4: New Study After Configuration Change")
|
|
print("="*70)
|
|
print("\nScenario: Bracket topology was modified, added new design variable")
|
|
print("Old surrogate is invalid -> Create NEW study with different name\n")
|
|
|
|
config_path = Path('examples/bracket/optimization_config_stress_displacement.json')
|
|
|
|
runner = OptimizationRunner(
|
|
config_path=config_path,
|
|
model_updater=bracket_model_updater,
|
|
simulation_runner=bracket_simulation_runner,
|
|
result_extractors={
|
|
'stress_extractor': stress_extractor,
|
|
'displacement_extractor': displacement_extractor
|
|
}
|
|
)
|
|
|
|
# Create a NEW study with a different name
|
|
# Version number (v2) indicates this is a different geometry/configuration
|
|
study = runner.run(
|
|
study_name="bracket_optimization_v2", # Different name!
|
|
n_trials=50,
|
|
resume=False # New study, not resuming
|
|
)
|
|
|
|
print("\nNew study created for modified configuration!")
|
|
print("Old study (v1) remains unchanged in database.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print("="*70)
|
|
print("STUDY MANAGEMENT DEMONSTRATION")
|
|
print("="*70)
|
|
print("\nThis script demonstrates study management features:")
|
|
print("1. Create new study")
|
|
print("2. Resume existing study (add more trials)")
|
|
print("3. List all studies")
|
|
print("4. Create new study after topology change")
|
|
print("\nREQUIREMENT: Simcenter3D must be OPEN")
|
|
print("="*70)
|
|
|
|
response = input("\nIs Simcenter3D open? (yes/no): ")
|
|
if response.lower() not in ['yes', 'y']:
|
|
print("Please open Simcenter3D and try again.")
|
|
sys.exit(0)
|
|
|
|
print("\n" + "="*70)
|
|
print("Which example would you like to run?")
|
|
print("="*70)
|
|
print("1. Create a new study (20 trials)")
|
|
print("2. Resume existing study 'bracket_optimization_v1' (+30 trials)")
|
|
print("3. List all available studies")
|
|
print("4. Create new study after topology change (50 trials)")
|
|
print("0. Exit")
|
|
print("="*70)
|
|
|
|
choice = input("\nEnter choice (0-4): ").strip()
|
|
|
|
try:
|
|
if choice == '1':
|
|
example_1_new_study()
|
|
elif choice == '2':
|
|
example_2_resume_study()
|
|
elif choice == '3':
|
|
example_3_list_studies()
|
|
elif choice == '4':
|
|
example_4_new_study_after_change()
|
|
elif choice == '0':
|
|
print("Exiting.")
|
|
else:
|
|
print("Invalid choice.")
|
|
|
|
except Exception as e:
|
|
print("\n" + "="*70)
|
|
print("ERROR")
|
|
print("="*70)
|
|
print(f"{e}")
|
|
import traceback
|
|
traceback.print_exc()
|