Files
Atomizer/optimization_engine/study_continuation.py
Anto01 e3bdb08a22 feat: Major update with validators, skills, dashboard, and docs reorganization
- Add validation framework (config, model, results, study validators)
- Add Claude Code skills (create-study, run-optimization, generate-report,
  troubleshoot, analyze-model)
- Add Atomizer Dashboard (React frontend + FastAPI backend)
- Reorganize docs into structured directories (00-09)
- Add neural surrogate modules and training infrastructure
- Add multi-objective optimization support

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-25 19:23:58 -05:00

265 lines
8.8 KiB
Python

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