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>
This commit is contained in:
264
optimization_engine/study/continuation.py
Normal file
264
optimization_engine/study/continuation.py
Normal file
@@ -0,0 +1,264 @@
|
||||
"""
|
||||
Study Continuation - Standard utility for continuing existing optimization studies.
|
||||
|
||||
This module provides a standardized way to continue optimization studies with
|
||||
additional trials, preserving all existing trial data and learned knowledge.
|
||||
|
||||
Usage:
|
||||
from optimization_engine.study.continuation import continue_study
|
||||
|
||||
continue_study(
|
||||
study_dir=Path("studies/my_study"),
|
||||
additional_trials=50,
|
||||
objective_function=my_objective,
|
||||
design_variables={'param1': (0, 10), 'param2': (0, 100)}
|
||||
)
|
||||
|
||||
This is an Atomizer standard feature that should be exposed in the dashboard
|
||||
alongside "Start New Optimization".
|
||||
"""
|
||||
|
||||
import optuna
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Dict, Tuple, Callable, Optional
|
||||
|
||||
|
||||
def continue_study(
|
||||
study_dir: Path,
|
||||
additional_trials: int,
|
||||
objective_function: Callable,
|
||||
design_variables: Optional[Dict[str, Tuple[float, float]]] = None,
|
||||
target_value: Optional[float] = None,
|
||||
tolerance: Optional[float] = None,
|
||||
verbose: bool = True
|
||||
) -> Dict:
|
||||
"""
|
||||
Continue an existing optimization study with additional trials.
|
||||
|
||||
Args:
|
||||
study_dir: Path to study directory containing 1_setup and 2_results
|
||||
additional_trials: Number of additional trials to run
|
||||
objective_function: Objective function to optimize (same as original)
|
||||
design_variables: Optional dict of design variable bounds (for reference)
|
||||
target_value: Optional target value for early stopping
|
||||
tolerance: Optional tolerance for target achievement
|
||||
verbose: Print progress information
|
||||
|
||||
Returns:
|
||||
Dict containing:
|
||||
- 'study': The Optuna study object
|
||||
- 'total_trials': Total number of trials after continuation
|
||||
- 'successful_trials': Number of successful trials
|
||||
- 'pruned_trials': Number of pruned trials
|
||||
- 'best_value': Best objective value achieved
|
||||
- 'best_params': Best parameters found
|
||||
- 'target_achieved': Whether target was achieved (if specified)
|
||||
|
||||
Raises:
|
||||
FileNotFoundError: If study database doesn't exist
|
||||
ValueError: If study name cannot be determined
|
||||
"""
|
||||
|
||||
# Setup paths
|
||||
setup_dir = study_dir / "1_setup"
|
||||
results_dir = study_dir / "2_results"
|
||||
history_file = results_dir / "optimization_history_incremental.json"
|
||||
|
||||
# Load workflow config to get study name
|
||||
workflow_file = setup_dir / "workflow_config.json"
|
||||
if not workflow_file.exists():
|
||||
raise FileNotFoundError(
|
||||
f"Workflow config not found: {workflow_file}. "
|
||||
f"Make sure this is a valid study directory."
|
||||
)
|
||||
|
||||
with open(workflow_file) as f:
|
||||
workflow = json.load(f)
|
||||
|
||||
study_name = workflow.get('study_name')
|
||||
if not study_name:
|
||||
raise ValueError("Study name not found in workflow_config.json")
|
||||
|
||||
# Load existing study
|
||||
storage = f"sqlite:///{results_dir / 'study.db'}"
|
||||
|
||||
try:
|
||||
study = optuna.load_study(study_name=study_name, storage=storage)
|
||||
except KeyError:
|
||||
raise FileNotFoundError(
|
||||
f"Study '{study_name}' not found in database. "
|
||||
f"Run the initial optimization first using run_optimization.py"
|
||||
)
|
||||
|
||||
# Get current state
|
||||
current_trials = len(study.trials)
|
||||
current_best = study.best_value if study.best_trial else None
|
||||
|
||||
if verbose:
|
||||
print("\n" + "="*70)
|
||||
print(" CONTINUING OPTIMIZATION STUDY")
|
||||
print("="*70)
|
||||
print(f"\n Study: {study_name}")
|
||||
print(f" Current trials: {current_trials}")
|
||||
if current_best is not None:
|
||||
print(f" Current best: {current_best:.6f}")
|
||||
print(f" Best params:")
|
||||
for param, value in study.best_params.items():
|
||||
print(f" {param}: {value:.4f}")
|
||||
print(f"\n Adding {additional_trials} trials...\n")
|
||||
|
||||
# Continue optimization
|
||||
study.optimize(
|
||||
objective_function,
|
||||
n_trials=additional_trials,
|
||||
timeout=None,
|
||||
catch=(Exception,) # Catch exceptions to allow graceful continuation
|
||||
)
|
||||
|
||||
# Analyze results
|
||||
total_trials = len(study.trials)
|
||||
successful_trials = len([t for t in study.trials if t.state == optuna.trial.TrialState.COMPLETE])
|
||||
pruned_trials = len([t for t in study.trials if t.state == optuna.trial.TrialState.PRUNED])
|
||||
|
||||
results = {
|
||||
'study': study,
|
||||
'total_trials': total_trials,
|
||||
'successful_trials': successful_trials,
|
||||
'pruned_trials': pruned_trials,
|
||||
'best_value': study.best_value,
|
||||
'best_params': study.best_params,
|
||||
}
|
||||
|
||||
# Check target achievement if specified
|
||||
if target_value is not None and tolerance is not None:
|
||||
target_achieved = abs(study.best_value - target_value) <= tolerance
|
||||
results['target_achieved'] = target_achieved
|
||||
|
||||
if verbose:
|
||||
print("\n" + "="*70)
|
||||
print(" CONTINUATION COMPLETE")
|
||||
print("="*70)
|
||||
print(f" Total trials: {total_trials} (added {additional_trials})")
|
||||
print(f" Successful: {successful_trials}")
|
||||
print(f" Pruned: {pruned_trials}")
|
||||
print(f" Pruning rate: {pruned_trials/total_trials*100:.1f}%")
|
||||
print(f"\n Best value: {study.best_value:.6f}")
|
||||
print(f" Best params:")
|
||||
for param, value in study.best_params.items():
|
||||
print(f" {param}: {value:.4f}")
|
||||
|
||||
if target_value is not None and tolerance is not None:
|
||||
target_achieved = results.get('target_achieved', False)
|
||||
print(f"\n Target: {target_value} ± {tolerance}")
|
||||
print(f" Target achieved: {'YES' if target_achieved else 'NO'}")
|
||||
|
||||
print("="*70 + "\n")
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def can_continue_study(study_dir: Path) -> Tuple[bool, str]:
|
||||
"""
|
||||
Check if a study can be continued.
|
||||
|
||||
Args:
|
||||
study_dir: Path to study directory
|
||||
|
||||
Returns:
|
||||
(can_continue, message): Tuple of bool and explanation message
|
||||
"""
|
||||
|
||||
setup_dir = study_dir / "1_setup"
|
||||
results_dir = study_dir / "2_results"
|
||||
|
||||
# Check if workflow config exists
|
||||
workflow_file = setup_dir / "workflow_config.json"
|
||||
if not workflow_file.exists():
|
||||
return False, f"No workflow_config.json found in {setup_dir}"
|
||||
|
||||
# Load study name
|
||||
try:
|
||||
with open(workflow_file) as f:
|
||||
workflow = json.load(f)
|
||||
study_name = workflow.get('study_name')
|
||||
if not study_name:
|
||||
return False, "No study_name in workflow_config.json"
|
||||
except Exception as e:
|
||||
return False, f"Error reading workflow config: {e}"
|
||||
|
||||
# Check if database exists
|
||||
db_file = results_dir / "study.db"
|
||||
if not db_file.exists():
|
||||
return False, f"No study.db found. Run initial optimization first."
|
||||
|
||||
# Try to load study
|
||||
try:
|
||||
storage = f"sqlite:///{db_file}"
|
||||
study = optuna.load_study(study_name=study_name, storage=storage)
|
||||
trial_count = len(study.trials)
|
||||
|
||||
if trial_count == 0:
|
||||
return False, "Study exists but has no trials yet"
|
||||
|
||||
return True, f"Study '{study_name}' ready (current trials: {trial_count})"
|
||||
|
||||
except KeyError:
|
||||
return False, f"Study '{study_name}' not found in database"
|
||||
except Exception as e:
|
||||
return False, f"Error loading study: {e}"
|
||||
|
||||
|
||||
def get_study_status(study_dir: Path) -> Optional[Dict]:
|
||||
"""
|
||||
Get current status of a study.
|
||||
|
||||
Args:
|
||||
study_dir: Path to study directory
|
||||
|
||||
Returns:
|
||||
Dict with study status info, or None if study doesn't exist
|
||||
{
|
||||
'study_name': str,
|
||||
'total_trials': int,
|
||||
'successful_trials': int,
|
||||
'pruned_trials': int,
|
||||
'best_value': float,
|
||||
'best_params': dict
|
||||
}
|
||||
"""
|
||||
|
||||
can_continue, message = can_continue_study(study_dir)
|
||||
|
||||
if not can_continue:
|
||||
return None
|
||||
|
||||
setup_dir = study_dir / "1_setup"
|
||||
results_dir = study_dir / "2_results"
|
||||
|
||||
# Load study
|
||||
with open(setup_dir / "workflow_config.json") as f:
|
||||
workflow = json.load(f)
|
||||
|
||||
study_name = workflow['study_name']
|
||||
storage = f"sqlite:///{results_dir / 'study.db'}"
|
||||
|
||||
try:
|
||||
study = optuna.load_study(study_name=study_name, storage=storage)
|
||||
|
||||
total_trials = len(study.trials)
|
||||
successful_trials = len([t for t in study.trials if t.state == optuna.trial.TrialState.COMPLETE])
|
||||
pruned_trials = len([t for t in study.trials if t.state == optuna.trial.TrialState.PRUNED])
|
||||
|
||||
return {
|
||||
'study_name': study_name,
|
||||
'total_trials': total_trials,
|
||||
'successful_trials': successful_trials,
|
||||
'pruned_trials': pruned_trials,
|
||||
'pruning_rate': pruned_trials / total_trials if total_trials > 0 else 0,
|
||||
'best_value': study.best_value if study.best_trial else None,
|
||||
'best_params': study.best_params if study.best_trial else None
|
||||
}
|
||||
|
||||
except Exception:
|
||||
return None
|
||||
Reference in New Issue
Block a user