Coverage for sparkle/CLI/run_solvers.py: 0%

83 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-09-27 09:10 +0000

1#!/usr/bin/env python3 

2"""Sparkle command to run solvers to get their performance data.""" 

3from __future__ import annotations 

4 

5import sys 

6import argparse 

7from pathlib import PurePath, Path 

8 

9import runrunner as rrr 

10from runrunner.base import Runner, Run 

11 

12from sparkle.CLI.help import global_variables as gv 

13from sparkle.structures import PerformanceDataFrame 

14from sparkle.CLI.help import logging as sl 

15from sparkle.platform.settings_objects import Settings, SettingState 

16from sparkle.platform import CommandName, COMMAND_DEPENDENCIES 

17from sparkle.CLI.initialise import check_for_initialise 

18from sparkle.CLI.help import argparse_custom as ac 

19 

20 

21def parser_function() -> argparse.ArgumentParser: 

22 """Define the command line arguments.""" 

23 parser = argparse.ArgumentParser() 

24 

25 parser.add_argument(*ac.RecomputeRunSolversArgument.names, 

26 **ac.RecomputeRunSolversArgument.kwargs) 

27 parser.add_argument(*ac.SparkleObjectiveArgument.names, 

28 **ac.SparkleObjectiveArgument.kwargs) 

29 parser.add_argument(*ac.TargetCutOffTimeRunSolversArgument.names, 

30 **ac.TargetCutOffTimeRunSolversArgument.kwargs) 

31 parser.add_argument(*ac.AlsoConstructSelectorAndReportArgument.names, 

32 **ac.AlsoConstructSelectorAndReportArgument.kwargs) 

33 parser.add_argument(*ac.RunOnArgument.names, 

34 **ac.RunOnArgument.kwargs) 

35 parser.add_argument(*ac.SettingsFileArgument.names, 

36 **ac.SettingsFileArgument.kwargs) 

37 

38 return parser 

39 

40 

41def running_solvers_performance_data( 

42 performance_data_csv_path: Path, 

43 num_job_in_parallel: int, 

44 rerun: bool = False, 

45 run_on: Runner = Runner.SLURM) -> Run: 

46 """Run the solvers for the performance data. 

47 

48 Parameters 

49 ---------- 

50 performance_data_csv_path: Path 

51 The path to the performance data file 

52 num_job_in_parallel: int 

53 The maximum number of jobs to run in parallel 

54 rerun: bool 

55 Run only solvers for which no data is available yet (False) or (re)run all 

56 solvers to get (new) performance data for them (True) 

57 run_on: Runner 

58 Where to execute the solvers. For available values see runrunner.base.Runner 

59 enum. Default: "Runner.SLURM". 

60 

61 Returns 

62 ------- 

63 run: runrunner.LocalRun or runrunner.SlurmRun 

64 If the run is local return a QueuedRun object with the information concerning 

65 the run. 

66 """ 

67 # Open the performance data csv file 

68 performance_dataframe = PerformanceDataFrame(performance_data_csv_path) 

69 # List of jobs to do 

70 jobs = performance_dataframe.get_job_list(rerun=rerun) 

71 num_jobs = len(jobs) 

72 

73 cutoff_time_str = str(gv.settings().get_general_target_cutoff_time()) 

74 

75 print(f"Cutoff time for each solver run: {cutoff_time_str} seconds") 

76 print(f"Total number of jobs to run: {num_jobs}") 

77 

78 # If there are no jobs, stop 

79 if num_jobs == 0: 

80 return None 

81 

82 if run_on == Runner.LOCAL: 

83 print("Running the solvers locally") 

84 elif run_on == Runner.SLURM: 

85 print("Running the solvers through Slurm") 

86 

87 sbatch_options = gv.settings().get_slurm_extra_options(as_args=True) 

88 srun_options = ["-N1", "-n1"] + sbatch_options 

89 objectives = gv.settings().get_general_sparkle_objectives() 

90 run_solvers_core = Path(__file__).parent.resolve() / "core" / "run_solvers_core.py" 

91 cmd_list = [f"{run_solvers_core} " 

92 f"--performance-data {performance_data_csv_path} " 

93 f"--instance {inst_p} --solver {solver_p} " 

94 f"--objectives {','.join([str(o) for o in objectives])} " 

95 f"--log-dir {sl.caller_log_dir}" for inst_p, _, solver_p in jobs] 

96 

97 run = rrr.add_to_queue( 

98 runner=run_on, 

99 cmd=cmd_list, 

100 parallel_jobs=num_job_in_parallel, 

101 name=CommandName.RUN_SOLVERS, 

102 base_dir=sl.caller_log_dir, 

103 sbatch_options=sbatch_options, 

104 srun_options=srun_options) 

105 

106 if run_on == Runner.LOCAL: 

107 # TODO: It would be nice to extract some info per job and print it 

108 # As the user now only sees jobs starting and completing without their results 

109 run.wait() 

110 

111 return run 

112 

113 

114def run_solvers_on_instances( 

115 recompute: bool = False, 

116 run_on: Runner = Runner.SLURM, 

117 also_construct_selector_and_report: bool = False) -> None: 

118 """Run all the solvers on all the instances that were not not previously run. 

119 

120 If recompute is True, rerun everything even if previously run. Where the solvers are 

121 executed can be controlled with "run_on". 

122 

123 Parameters 

124 ---------- 

125 recompute: bool 

126 If True, recompute all solver-instance pairs even if they were run before. 

127 Default: False 

128 run_on: Runner 

129 On which computer or cluster environment to run the solvers. 

130 Available: Runner.LOCAL, Runner.SLURM. Default: Runner.SLURM 

131 also_construct_selector_and_report: bool 

132 If True, the selector will be constructed and a report will be produced. 

133 """ 

134 if recompute: 

135 PerformanceDataFrame(gv.settings().DEFAULT_performance_data_path).clean_csv() 

136 num_job_in_parallel = gv.settings().get_number_of_jobs_in_parallel() 

137 

138 runs = [running_solvers_performance_data( 

139 performance_data_csv_path=gv.settings().DEFAULT_performance_data_path, 

140 num_job_in_parallel=num_job_in_parallel, 

141 rerun=recompute, 

142 run_on=run_on)] 

143 

144 # If there are no jobs return 

145 if all(run is None for run in runs): 

146 print("Running solvers done!") 

147 return 

148 

149 sbatch_user_options = gv.settings().get_slurm_extra_options(as_args=True) 

150 if also_construct_selector_and_report: 

151 runs.append(rrr.add_to_queue( 

152 runner=run_on, 

153 cmd="sparkle/CLI/construct_portfolio_selector.py", 

154 name=CommandName.CONSTRUCT_PORTFOLIO_SELECTOR, 

155 dependencies=runs[-1], 

156 base_dir=sl.caller_log_dir, 

157 sbatch_options=sbatch_user_options)) 

158 

159 runs.append(rrr.add_to_queue( 

160 runner=run_on, 

161 cmd="sparkle/CLI/generate_report.py", 

162 name=CommandName.GENERATE_REPORT, 

163 dependencies=runs[-1], 

164 base_dir=sl.caller_log_dir, 

165 sbatch_options=sbatch_user_options)) 

166 

167 if run_on == Runner.LOCAL: 

168 print("Waiting for the local calculations to finish.") 

169 for run in runs: 

170 if run is not None: 

171 run.wait() 

172 print("Running solvers done!") 

173 elif run_on == Runner.SLURM: 

174 print("Running solvers. Waiting for Slurm job(s) with id(s): " 

175 f'{",".join(r.run_id for r in runs if r is not None)}') 

176 

177 

178if __name__ == "__main__": 

179 # Log command call 

180 sl.log_command(sys.argv) 

181 

182 # Define command line arguments 

183 parser = parser_function() 

184 

185 # Process command line arguments 

186 args = parser.parse_args() 

187 

188 if args.settings_file is not None: 

189 # Do first, so other command line options can override settings from the file 

190 gv.settings().read_settings_ini(args.settings_file, SettingState.CMD_LINE) 

191 

192 if args.objectives is not None: 

193 gv.settings().set_general_sparkle_objectives( 

194 args.objectives, SettingState.CMD_LINE 

195 ) 

196 

197 if args.target_cutoff_time is not None: 

198 gv.settings().set_general_target_cutoff_time( 

199 args.target_cutoff_time, SettingState.CMD_LINE) 

200 

201 if args.run_on is not None: 

202 gv.settings().set_run_on( 

203 args.run_on.value, SettingState.CMD_LINE) 

204 run_on = gv.settings().get_run_on() 

205 

206 check_for_initialise(COMMAND_DEPENDENCIES[CommandName.RUN_SOLVERS]) 

207 

208 # Compare current settings to latest.ini 

209 prev_settings = Settings(PurePath("Settings/latest.ini")) 

210 Settings.check_settings_changes(gv.settings(), prev_settings) 

211 

212 print("Start running solvers ...") 

213 

214 # Write settings to file before starting, since they are used in callback scripts 

215 gv.settings().write_used_settings() 

216 

217 run_solvers_on_instances( 

218 recompute=args.recompute, 

219 also_construct_selector_and_report=args.also_construct_selector_and_report, 

220 run_on=run_on)