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
« 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
5import sys
6import argparse
7from pathlib import PurePath, Path
9import runrunner as rrr
10from runrunner.base import Runner, Run
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
21def parser_function() -> argparse.ArgumentParser:
22 """Define the command line arguments."""
23 parser = argparse.ArgumentParser()
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)
38 return parser
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.
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".
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)
73 cutoff_time_str = str(gv.settings().get_general_target_cutoff_time())
75 print(f"Cutoff time for each solver run: {cutoff_time_str} seconds")
76 print(f"Total number of jobs to run: {num_jobs}")
78 # If there are no jobs, stop
79 if num_jobs == 0:
80 return None
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")
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]
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)
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()
111 return run
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.
120 If recompute is True, rerun everything even if previously run. Where the solvers are
121 executed can be controlled with "run_on".
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()
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)]
144 # If there are no jobs return
145 if all(run is None for run in runs):
146 print("Running solvers done!")
147 return
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))
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))
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)}')
178if __name__ == "__main__":
179 # Log command call
180 sl.log_command(sys.argv)
182 # Define command line arguments
183 parser = parser_function()
185 # Process command line arguments
186 args = parser.parse_args()
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)
192 if args.objectives is not None:
193 gv.settings().set_general_sparkle_objectives(
194 args.objectives, SettingState.CMD_LINE
195 )
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)
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()
206 check_for_initialise(COMMAND_DEPENDENCIES[CommandName.RUN_SOLVERS])
208 # Compare current settings to latest.ini
209 prev_settings = Settings(PurePath("Settings/latest.ini"))
210 Settings.check_settings_changes(gv.settings(), prev_settings)
212 print("Start running solvers ...")
214 # Write settings to file before starting, since they are used in callback scripts
215 gv.settings().write_used_settings()
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)