#!/usr/bin/env python """ Atomizer Best Design Archiver Archives the best design iteration folder to 3_results/best_design_archive/ with timestamp. Should be called at the end of every optimization run. Usage: python tools/archive_best_design.py python tools/archive_best_design.py m1_mirror_adaptive_V14 # Or call from run_optimization.py at the end: from tools.archive_best_design import archive_best_design archive_best_design("my_study") Author: Atomizer Created: 2025-12-12 """ import argparse import json import shutil import sqlite3 from datetime import datetime from pathlib import Path import sys # Add parent to path for imports sys.path.insert(0, str(Path(__file__).parent.parent)) def find_study_path(study_name: str) -> Path: """Find study directory by name.""" # Check if already a path if Path(study_name).exists(): return Path(study_name) # Check relative to tools dir studies_dir = Path(__file__).parent.parent / "studies" study_path = studies_dir / study_name if not study_path.exists(): raise FileNotFoundError(f"Study not found: {study_path}") return study_path def get_best_trial_info(study_path: Path) -> dict: """Get the best trial information from the database.""" db_path = study_path / "3_results" / "study.db" if not db_path.exists(): raise FileNotFoundError(f"Database not found: {db_path}") conn = sqlite3.connect(str(db_path)) cursor = conn.cursor() # Get trial with best weighted_sum cursor.execute(""" SELECT t.number, tua_ws.value_json as weighted_sum, tua_40.value_json as obj_40, tua_60.value_json as obj_60, tua_mfg.value_json as obj_mfg, tua_src.value_json as source FROM trials t LEFT JOIN trial_user_attributes tua_ws ON t.trial_id = tua_ws.trial_id AND tua_ws.key = 'weighted_sum' LEFT JOIN trial_user_attributes tua_40 ON t.trial_id = tua_40.trial_id AND tua_40.key = 'rel_filtered_rms_40_vs_20' LEFT JOIN trial_user_attributes tua_60 ON t.trial_id = tua_60.trial_id AND tua_60.key = 'rel_filtered_rms_60_vs_20' LEFT JOIN trial_user_attributes tua_mfg ON t.trial_id = tua_mfg.trial_id AND tua_mfg.key = 'mfg_90_optician_workload' LEFT JOIN trial_user_attributes tua_src ON t.trial_id = tua_src.trial_id AND tua_src.key = 'source' WHERE t.state = 'COMPLETE' AND CAST(tua_ws.value_json AS REAL) < 1000 ORDER BY CAST(tua_ws.value_json AS REAL) ASC LIMIT 1 """) result = cursor.fetchone() if not result: # Fallback to trial_values objective 0 cursor.execute(""" SELECT t.number, tv.value FROM trials t JOIN trial_values tv ON t.trial_id = tv.trial_id AND tv.objective = 0 WHERE t.state = 'COMPLETE' AND tv.value < 1000 ORDER BY tv.value ASC LIMIT 1 """) result = cursor.fetchone() if result: conn.close() return { "trial_number": result[0], "weighted_sum": result[1], "objectives": {}, "source": "unknown" } raise ValueError("No valid trials found") conn.close() return { "trial_number": result[0], "weighted_sum": float(result[1]) if result[1] else None, "objectives": { "rel_filtered_rms_40_vs_20": float(result[2]) if result[2] else None, "rel_filtered_rms_60_vs_20": float(result[3]) if result[3] else None, "mfg_90_optician_workload": float(result[4]) if result[4] else None, }, "source": json.loads(result[5]) if result[5] else "unknown" } def trial_to_iteration(study_path: Path, trial_number: int) -> int: """Convert trial number to iteration number.""" db_path = study_path / "3_results" / "study.db" conn = sqlite3.connect(str(db_path)) cursor = conn.cursor() cursor.execute(""" SELECT t.number FROM trials t JOIN trial_user_attributes tua ON t.trial_id = tua.trial_id WHERE t.state = 'COMPLETE' AND tua.key = 'source' AND tua.value_json = '"FEA"' ORDER BY t.number """) fea_trials = [r[0] for r in cursor.fetchall()] conn.close() if trial_number not in fea_trials: raise ValueError(f"Trial {trial_number} is not an FEA trial (seeded data has no iteration folder)") return fea_trials.index(trial_number) + 1 def archive_best_design(study_name: str, verbose: bool = True) -> dict: """ Archive the best design to 3_results/best_design_archive// Args: study_name: Name of the study or path to study directory verbose: Print progress messages Returns: Dictionary with archive info """ study_path = find_study_path(study_name) # Get best trial info best_trial = get_best_trial_info(study_path) if best_trial["source"] != "FEA": if verbose: print(f"[WARN] Best trial #{best_trial['trial_number']} is from seeded data ({best_trial['source']})") print("[WARN] No iteration folder to archive. Skipping.") return { "success": False, "reason": "Best trial is from seeded data, no iteration folder", "trial_number": best_trial["trial_number"], "source": best_trial["source"] } # Find iteration folder iter_num = trial_to_iteration(study_path, best_trial["trial_number"]) iter_folder = f"iter{iter_num}" iter_path = study_path / "2_iterations" / iter_folder if not iter_path.exists(): raise FileNotFoundError(f"Iteration folder not found: {iter_path}") # Create archive directory with timestamp timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") archive_dir = study_path / "3_results" / "best_design_archive" / timestamp archive_dir.mkdir(parents=True, exist_ok=True) if verbose: print(f"[INFO] Archiving best design...") print(f" Trial: #{best_trial['trial_number']}") print(f" Iteration: {iter_folder}") print(f" Weighted Sum: {best_trial['weighted_sum']:.2f}") print(f" Archive: {archive_dir}") # Copy all files from iteration folder files_copied = 0 for src_file in iter_path.iterdir(): dst_file = archive_dir / src_file.name if src_file.is_file(): shutil.copy2(src_file, dst_file) files_copied += 1 elif src_file.is_dir(): shutil.copytree(src_file, dst_file) files_copied += 1 # Write metadata file metadata = { "study_name": study_path.name, "trial_number": best_trial["trial_number"], "iteration_folder": iter_folder, "weighted_sum": best_trial["weighted_sum"], "objectives": best_trial["objectives"], "source": best_trial["source"], "archived_at": datetime.now().isoformat(), "files_copied": files_copied } metadata_path = archive_dir / "_archive_info.json" with open(metadata_path, "w") as f: json.dump(metadata, f, indent=2) if verbose: print(f"[OK] Archived {files_copied} files to {archive_dir.name}/") return { "success": True, "archive_path": str(archive_dir), "trial_number": best_trial["trial_number"], "iteration_folder": iter_folder, "weighted_sum": best_trial["weighted_sum"], "files_copied": files_copied } def main(): parser = argparse.ArgumentParser( description="Archive best design iteration folder", epilog="This should be run at the end of every optimization." ) parser.add_argument("study_name", help="Name of the study to archive") parser.add_argument("--quiet", "-q", action="store_true", help="Suppress output") parser.add_argument("--json", "-j", action="store_true", help="Output as JSON") args = parser.parse_args() try: result = archive_best_design(args.study_name, verbose=not args.quiet) if args.json: print(json.dumps(result, indent=2)) elif result["success"]: print(f"\n[SUCCESS] Best design archived to: {result['archive_path']}") except Exception as e: print(f"[ERROR] {e}") sys.exit(1) if __name__ == "__main__": main()