Coverage for sparkle/CLI/add_solver.py: 81%
90 statements
« prev ^ index » next coverage.py v7.6.4, created at 2024-11-05 14:48 +0000
« prev ^ index » next coverage.py v7.6.4, created at 2024-11-05 14:48 +0000
1#!/usr/bin/env python3
2"""Sparkle command to add a solver to the Sparkle platform."""
3import os
4import stat
5import sys
6import argparse
7import shutil
8from pathlib import Path
10import runrunner as rrr
12from sparkle.platform import file_help as sfh
13from sparkle.CLI.help import global_variables as gv
14from sparkle.structures import PerformanceDataFrame
15from sparkle.CLI.run_solvers import running_solvers_performance_data
16from sparkle.solver import Solver
17from sparkle.CLI.help import logging as sl
18from sparkle.platform import CommandName, COMMAND_DEPENDENCIES
19from sparkle.CLI.initialise import check_for_initialise
20from sparkle.CLI.help import argparse_custom as ac
21from sparkle.platform.settings_objects import SettingState
24def parser_function() -> argparse.ArgumentParser:
25 """Define the command line arguments."""
26 parser = argparse.ArgumentParser(
27 description="Add a solver to the Sparkle platform.")
28 parser.add_argument(*ac.DeterministicArgument.names,
29 **ac.DeterministicArgument.kwargs)
30 parser.add_argument(*ac.RunSolverNowArgument.names,
31 **ac.RunSolverNowArgument.kwargs)
32 parser.add_argument(*ac.NicknameSolverArgument.names,
33 **ac.NicknameSolverArgument.kwargs)
34 parser.add_argument(*ac.SolverPathArgument.names,
35 **ac.SolverPathArgument.kwargs)
36 parser.add_argument(*ac.RunOnArgument.names,
37 **ac.RunOnArgument.kwargs)
38 parser.add_argument(*ac.SkipChecksArgument.names,
39 **ac.SkipChecksArgument.kwargs)
40 return parser
43def main(argv: list[str]) -> None:
44 """Main function of the command."""
45 # Log command call
46 sl.log_command(sys.argv)
48 # Define command line arguments
49 parser = parser_function()
51 # Process command line arguments
52 args = parser.parse_args(argv)
53 solver_source = Path(args.solver_path)
54 deterministic = args.deterministic
56 check_for_initialise(COMMAND_DEPENDENCIES[CommandName.ADD_SOLVER])
58 if not solver_source.exists():
59 print(f'Solver path "{solver_source}" does not exist!')
60 sys.exit(-1)
62 nickname = args.nickname
64 if args.run_on is not None:
65 gv.settings().set_run_on(
66 args.run_on.value, SettingState.CMD_LINE)
67 run_on = gv.settings().get_run_on()
69 if args.run_checks:
70 print("Running checks...")
71 solver = Solver(Path(solver_source))
72 pcs_file = solver.get_pcs_file()
73 if pcs_file is None:
74 print("None or multiple .pcs files found. Solver "
75 "is not valid for configuration.")
76 else:
77 print(f"One pcs file detected: {pcs_file.name}. ", end="")
78 if solver.read_pcs_file():
79 print("Can read the pcs file.")
80 else:
81 print("WARNING: Can not read the provided pcs file format.")
83 configurator_wrapper_path = solver_source / Solver.wrapper
84 if not (configurator_wrapper_path.is_file()
85 and os.access(configurator_wrapper_path, os.X_OK)):
86 print(f"WARNING: Solver {solver_source.name} does not have a solver wrapper "
87 f"(Missing file {Solver.wrapper}) or is not executable. ")
89 # Start add solver
90 solver_directory = gv.settings().DEFAULT_solver_dir / solver_source.name
91 if not solver_directory.exists():
92 solver_directory.mkdir(parents=True, exist_ok=True)
93 else:
94 print(f"ERROR: Solver {solver_source.name} already exists! "
95 "Can not add new solver.")
96 sys.exit(-1)
97 shutil.copytree(solver_source, solver_directory, dirs_exist_ok=True)
98 # Save the deterministic bool in the solver
99 with (solver_directory / Solver.meta_data).open("w+") as fout:
100 fout.write(str({"deterministic": deterministic}))
102 # Add RunSolver executable to the solver
103 runsolver_path = gv.settings().DEFAULT_runsolver_exec
104 if runsolver_path.name in [file.name for file in solver_directory.iterdir()]:
105 print("Warning! RunSolver executable detected in Solver "
106 f"{solver_source.name}. This will be replaced with "
107 f"Sparkle's version of RunSolver. ({runsolver_path})")
108 runsolver_target = solver_directory / runsolver_path.name
109 shutil.copyfile(runsolver_path, runsolver_target)
110 runsolver_target.chmod(os.stat(runsolver_target).st_mode | stat.S_IEXEC)
112 performance_data = PerformanceDataFrame(
113 gv.settings().DEFAULT_performance_data_path,
114 objectives=gv.settings().get_general_sparkle_objectives())
115 performance_data.add_solver(solver_directory)
116 performance_data.save_csv()
118 print(f"Adding solver {solver_source.name} done!")
120 if nickname is not None:
121 sfh.add_remove_platform_item(solver_directory,
122 gv.solver_nickname_list_path, key=nickname)
124 solver = Solver(solver_directory) # Recreate solver from its new directory
125 if solver.get_pcs_file() is not None:
126 print("Generating missing PCS files...")
127 solver.port_pcs("IRACE") # Create PCS file for IRACE
128 print("Generating done!")
130 if args.run_solver_now:
131 num_job_in_parallel = gv.settings().get_number_of_jobs_in_parallel()
132 dependency_run_list = [running_solvers_performance_data(
133 gv.settings().DEFAULT_performance_data_path, num_job_in_parallel,
134 rerun=False, run_on=run_on
135 )]
137 sbatch_options = gv.settings().get_slurm_extra_options(as_args=True)
138 srun_options = ["-N1", "-n1"] + sbatch_options
139 run_construct_portfolio_selector = rrr.add_to_queue(
140 cmd="sparkle/CLI/construct_portfolio_selector.py",
141 name=CommandName.CONSTRUCT_PORTFOLIO_SELECTOR,
142 dependencies=dependency_run_list,
143 base_dir=sl.caller_log_dir,
144 sbatch_options=sbatch_options,
145 srun_options=srun_options)
147 dependency_run_list.append(run_construct_portfolio_selector)
149 rrr.add_to_queue(
150 cmd="sparkle/CLI/generate_report.py",
151 name=CommandName.GENERATE_REPORT,
152 dependencies=dependency_run_list,
153 base_dir=sl.caller_log_dir,
154 sbatch_options=sbatch_options,
155 srun_options=srun_options)
157 # Write used settings to file
158 gv.settings().write_used_settings()
159 sys.exit(0)
162if __name__ == "__main__":
163 main(sys.argv[1:])