#!/usr/bin/env python """ Atomizer Best Iteration Finder Finds the iteration folder containing the best design for a study. Useful for extracting the best model files after optimization. Usage: python tools/find_best_iteration.py python tools/find_best_iteration.py m1_mirror_adaptive_V14 python tools/find_best_iteration.py m1_mirror_adaptive_V14 --copy-to ./best_design Author: Atomizer Created: 2025-12-12 """ import argparse import json import shutil import sqlite3 from pathlib import Path import sys def find_study_path(study_name: str) -> Path: """Find study directory by name.""" 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(study_path: Path) -> dict: """Get the best trial 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 (or lowest objective 0 value) cursor.execute(""" SELECT t.number, tua_ws.value_json, tua_src.value_json 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_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 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 { "number": result[0], "weighted_sum": result[1], "source": "unknown" } raise ValueError("No valid trials found") conn.close() return { "number": result[0], "weighted_sum": float(result[1]) if result[1] else None, "source": json.loads(result[2]) if result[2] 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() # Get all FEA trial numbers in order 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 find_best_iteration(study_name: str) -> dict: """Find the best iteration folder for a study.""" study_path = find_study_path(study_name) best_trial = get_best_trial(study_path) result = { "study_name": study_name, "trial_number": best_trial["number"], "weighted_sum": best_trial["weighted_sum"], "source": best_trial["source"], "iteration_folder": None, "iteration_path": None } # Only FEA trials have iteration folders if best_trial["source"] == "FEA": iter_num = trial_to_iteration(study_path, best_trial["number"]) iter_folder = f"iter{iter_num}" iter_path = study_path / "2_iterations" / iter_folder if iter_path.exists(): result["iteration_folder"] = iter_folder result["iteration_path"] = str(iter_path) return result def copy_best_iteration(study_name: str, dest_path: str): """Copy the best iteration folder to a destination.""" result = find_best_iteration(study_name) if not result["iteration_path"]: raise ValueError(f"Best trial ({result['trial_number']}) is from seeded data, no iteration folder") src = Path(result["iteration_path"]) dst = Path(dest_path) if dst.exists(): print(f"Removing existing destination: {dst}") shutil.rmtree(dst) print(f"Copying {src} to {dst}") shutil.copytree(src, dst) return result def main(): parser = argparse.ArgumentParser(description="Find best iteration folder for Atomizer study") parser.add_argument("study_name", help="Name of the study") parser.add_argument("--copy-to", "-c", help="Copy best iteration folder to this path") parser.add_argument("--json", "-j", action="store_true", help="Output as JSON") args = parser.parse_args() try: if args.copy_to: result = copy_best_iteration(args.study_name, args.copy_to) print(f"\nCopied best iteration to: {args.copy_to}") else: result = find_best_iteration(args.study_name) if args.json: print(json.dumps(result, indent=2)) else: print(f"\n{'='*60}") print(f"BEST ITERATION FOR: {result['study_name']}") print(f"{'='*60}") print(f" Trial Number: {result['trial_number']}") print(f" Weighted Sum: {result['weighted_sum']:.2f}" if result['weighted_sum'] else " Weighted Sum: N/A") print(f" Source: {result['source']}") print(f" Iteration Folder: {result['iteration_folder'] or 'N/A (seeded data)'}") if result['iteration_path']: print(f" Full Path: {result['iteration_path']}") print(f"{'='*60}") except Exception as e: print(f"Error: {e}") sys.exit(1) if __name__ == "__main__": main()