# ShinkaEvolve: results_dir 传递流程详解 ## 🎯 问题 当运行 ShinkaEvolve 时,评估脚本(evaluate.py)是如何知道要把结果保存到指定的 `results_dir` 的? ## 📊 完整数据流程 ``` ┌─────────────────────────────────────────────────────────────────────────┐ │ 第 1 步: 用户配置 (run_circle_packing_*.py) │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ evo_config = EvolutionConfig( │ │ results_dir="examples/circle_packing/results/results_exp_20260129", │ │ ... │ │ ) │ │ │ └────────────────────────────┬────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 第 2 步: EvolutionRunner 初始化 (shinka/core/runner.py:90-108) │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ class EvolutionRunner: │ │ def __init__(self, evo_config, ...): │ │ if evo_config.results_dir is None: │ │ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") │ │ self.results_dir = f"results_{timestamp}" │ │ else: │ │ self.results_dir = Path(evo_config.results_dir) ← 使用配置 │ │ │ │ # 创建目录 │ │ Path(self.results_dir).mkdir(parents=True, exist_ok=True) │ │ │ │ 结果: self.results_dir = "examples/.../results_exp_20260129" │ │ │ └────────────────────────────┬────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 第 3 步: 运行每一代 (shinka/core/runner.py:461-496) │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ def _run_generation_0(self): │ │ # 构建当前代的目录路径 │ │ initial_dir = f"{self.results_dir}/{FOLDER_PREFIX}_0" │ │ # 即: "examples/.../results_exp_20260129/gen_0" │ │ │ │ Path(initial_dir).mkdir(parents=True, exist_ok=True) │ │ exec_fname = f"{initial_dir}/main.py" │ │ │ │ # 构建结果子目录 │ │ results_dir = f"{self.results_dir}/{FOLDER_PREFIX}_0/results" │ │ # 即: "examples/.../results_exp_20260129/gen_0/results" │ │ │ │ Path(results_dir).mkdir(parents=True, exist_ok=True) │ │ │ │ # 运行评估,传递 results_dir │ │ results, rtime = self.scheduler.run(exec_fname, results_dir) ←关键 │ │ │ └────────────────────────────┬────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 第 4 步: JobScheduler 构建命令 (shinka/launch/scheduler.py:98-148) │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ def _build_command(self, exec_fname_t, results_dir_t): │ │ cmd = [ │ │ "python", │ │ f"{self.config.eval_program_path}", ← 评估脚本路径 │ │ "--program_path", │ │ f"{exec_fname_t}", ← 被评估的程序 │ │ "--results_dir", │ │ results_dir_t, ← 结果目录! │ │ ] │ │ │ │ 实际命令: │ │ python examples/circle_packing/evaluate_with_auxiliary.py \ │ │ --program_path examples/.../gen_0/main.py \ │ │ --results_dir examples/.../gen_0/results │ │ │ └────────────────────────────┬────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 第 5 步: 评估脚本接收参数 (evaluate_with_auxiliary.py:260-295) │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ if __name__ == "__main__": │ │ parser = argparse.ArgumentParser(...) │ │ parser.add_argument( │ │ "--program_path", │ │ type=str, │ │ default="initial.py", │ │ ) │ │ parser.add_argument( │ │ "--results_dir", ← 接收命令行参数 │ │ type=str, │ │ default="results", │ │ ) │ │ │ │ args = parser.parse_args() │ │ │ │ main( │ │ program_path=args.program_path, │ │ results_dir=args.results_dir, ← 传递给 main 函数 │ │ ) │ │ │ └────────────────────────────┬────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 第 6 步: 评估执行和结果保存 (evaluate_with_auxiliary.py:161-256) │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ def main(program_path, results_dir, ...): │ │ # 确保目录存在 │ │ os.makedirs(results_dir, exist_ok=True) │ │ │ │ # 运行评估 │ │ metrics, correct, error = run_shinka_eval( │ │ program_path=program_path, │ │ results_dir=results_dir, ← 传递给核心评估函数 │ │ ... │ │ ) │ │ │ └────────────────────────────┬────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ 第 7 步: 核心评估函数保存结果 (shinka/core/wrap_eval.py:54-193) │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ def run_shinka_eval(program_path, results_dir, ...): │ │ # ... 运行实验 ... │ │ │ │ # 保存额外数据 │ │ if "extra_data" in metrics: │ │ os.makedirs(results_dir, exist_ok=True) │ │ extra_file = os.path.join(results_dir, "extra.pkl") │ │ with open(extra_file, "wb") as f: │ │ pickle.dump(extra_data, f) │ │ │ │ # 保存主要结果 │ │ save_json_results(results_dir, metrics, correct, error) │ │ │ │ def save_json_results(results_dir, metrics, correct, error): │ │ os.makedirs(results_dir, exist_ok=True) │ │ metrics_file = os.path.join(results_dir, "metrics.json") │ │ correct_file = os.path.join(results_dir, "correct.json") │ │ │ │ # 保存文件 │ │ with open(metrics_file, "w") as f: │ │ json.dump(metrics, f, indent=4) │ │ with open(correct_file, "w") as f: │ │ json.dump({"correct": correct, "error": error}, f, indent=4) │ │ │ │ 结果文件: │ │ - examples/.../gen_0/results/metrics.json │ │ - examples/.../gen_0/results/correct.json │ │ - examples/.../gen_0/results/extra.pkl (如果有) │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ``` ## 🔍 关键代码位置 ### 1. EvolutionConfig 定义 (shinka/core/runner.py:38-64) ```python @dataclass class EvolutionConfig: # ... 其他配置 ... results_dir: Optional[str] = None # 第59行:results_dir 配置项 # ... 其他配置 ... ``` ### 2. EvolutionRunner 使用配置 (shinka/core/runner.py:104-108) ```python if evo_config.results_dir is None: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") self.results_dir = f"results_{timestamp}" else: self.results_dir = Path(evo_config.results_dir) # 使用用户配置 ``` ### 3. 构建每一代的路径 (shinka/core/runner.py:461-496) ```python def _run_generation_0(self): initial_dir = f"{self.results_dir}/{FOLDER_PREFIX}_0" # ... results_dir = f"{self.results_dir}/{FOLDER_PREFIX}_0/results" Path(results_dir).mkdir(parents=True, exist_ok=True) # 运行评估 results, rtime = self.scheduler.run(exec_fname, results_dir) ``` ### 4. JobScheduler 构建命令 (shinka/launch/scheduler.py:98-148) ```python def _build_command(self, exec_fname_t: str, results_dir_t: str) -> List[str]: cmd = [ "python", f"{self.config.eval_program_path}", "--program_path", f"{exec_fname_t}", "--results_dir", results_dir_t, # ← results_dir 作为命令行参数 ] return cmd ``` ### 5. 评估脚本接收参数 (evaluate_with_auxiliary.py:260-295) ```python if __name__ == "__main__": parser = argparse.ArgumentParser(...) parser.add_argument( "--program_path", type=str, default="initial.py", ) parser.add_argument( "--results_dir", # ← 接收命令行参数 type=str, default="results", ) args = parser.parse_args() main( program_path=args.program_path, results_dir=args.results_dir, # ← 传递给 main ) ``` ### 6. 保存结果 (shinka/core/wrap_eval.py:33-51) ```python def save_json_results( results_dir: str, # ← 接收 results_dir metrics: Dict[str, Any], correct: bool, error: Optional[str] = None, ) -> None: os.makedirs(results_dir, exist_ok=True) metrics_file = os.path.join(results_dir, "metrics.json") correct_file = os.path.join(results_dir, "correct.json") with open(metrics_file, "w", encoding="utf-8") as f: json.dump(metrics, f, indent=4) with open(correct_file, "w", encoding="utf-8") as f: json.dump({"correct": correct, "error": error}, f, indent=4) ``` ## 📁 实际目录结构示例 ``` examples/circle_packing/results/results_exp_20260129_150530/ │ ├── evolution_db_exp_20260129_150530.sqlite ← 数据库 ├── evolution_run.log ← 日志 ├── experiment_config.yaml ← 配置 │ ├── gen_0/ ← 第 0 代 │ ├── main.py ← 被评估的程序 │ └── results/ ← 评估结果目录 │ ├── metrics.json ← 性能指标 │ ├── correct.json ← 正确性标志 │ ├── extra.npz ← 额外数据 │ ├── packing_viz.png ← 可视化 │ └── auxiliary_analysis.json ← 辅助分析 │ ├── gen_1/ │ ├── main.py │ └── results/ │ ├── metrics.json │ └── ... │ ├── gen_2/ │ └── ... │ └── best/ ← 最佳结果(符号链接) └── results/ ├── metrics.json └── ... ``` ## 🔧 关键设计特点 ### 1. 层次化的目录结构 ``` {results_dir}/ ├── gen_0/results/ ← EvolutionRunner 创建 ├── gen_1/results/ ← EvolutionRunner 创建 └── gen_2/results/ ← EvolutionRunner 创建 └── metrics.json ← 评估脚本保存 ``` ### 2. 参数传递链 ``` 用户配置 → EvolutionConfig.results_dir → EvolutionRunner.results_dir → JobScheduler.run(results_dir) → 命令行参数 --results_dir → argparse 解析 → main(results_dir) → save_json_results(results_dir) → 文件保存 ``` ### 3. 职责分离 | 组件 | 职责 | |-----|------| | **EvolutionRunner** | 创建顶层目录和每代的子目录 | | **JobScheduler** | 构建命令并传递 results_dir 参数 | | **评估脚本** | 接收参数并执行评估 | | **run_shinka_eval** | 保存评估结果到指定目录 | ## 💡 为什么这样设计? ### 优点 1. **解耦**: 评估脚本不需要知道 ShinkaEvolve 的目录结构 2. **灵活**: 可以独立运行评估脚本进行测试 3. **标准化**: 所有评估脚本使用相同的参数接口 4. **可追溯**: 每一代都有独立的结果目录 ### 示例:独立运行评估脚本 ```bash # 可以完全独立于 ShinkaEvolve 运行评估 python examples/circle_packing/evaluate_with_auxiliary.py \ --program_path my_solution.py \ --results_dir my_custom_results/ # 结果会保存在 my_custom_results/ 而不是 ShinkaEvolve 的目录结构中 ``` ## 🎯 总结 **评估脚本通过命令行参数 `--results_dir` 知道保存位置!** 整个流程是: 1. 用户在 `EvolutionConfig` 中设置 `results_dir` 2. `EvolutionRunner` 读取配置并创建目录结构 3. 对于每一代,构建 `{results_dir}/gen_X/results` 路径 4. `JobScheduler` 将这个路径作为 `--results_dir` 参数传递给评估脚本 5. 评估脚本通过 `argparse` 解析参数 6. `run_shinka_eval` 和 `save_json_results` 使用这个路径保存结果 这是一个**标准的命令行参数传递机制**,而不是硬编码或环境变量。 --- **创建时间**: 2026-01-29 **相关文件**: - `shinka/core/runner.py` - EvolutionRunner 和 EvolutionConfig - `shinka/launch/scheduler.py` - JobScheduler 命令构建 - `shinka/core/wrap_eval.py` - run_shinka_eval 和结果保存 - `examples/circle_packing/evaluate_with_auxiliary.py` - 评估脚本示例