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