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

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 

9 

10import runrunner as rrr 

11 

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 

22 

23 

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 

41 

42 

43def main(argv: list[str]) -> None: 

44 """Main function of the command.""" 

45 # Log command call 

46 sl.log_command(sys.argv) 

47 

48 # Define command line arguments 

49 parser = parser_function() 

50 

51 # Process command line arguments 

52 args = parser.parse_args(argv) 

53 solver_source = Path(args.solver_path) 

54 deterministic = args.deterministic 

55 

56 check_for_initialise(COMMAND_DEPENDENCIES[CommandName.ADD_SOLVER]) 

57 

58 if not solver_source.exists(): 

59 print(f'Solver path "{solver_source}" does not exist!') 

60 sys.exit(-1) 

61 

62 nickname = args.nickname 

63 

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() 

68 

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.") 

82 

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. ") 

88 

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})) 

101 

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) 

111 

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() 

117 

118 print(f"Adding solver {solver_source.name} done!") 

119 

120 if nickname is not None: 

121 sfh.add_remove_platform_item(solver_directory, 

122 gv.solver_nickname_list_path, key=nickname) 

123 

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!") 

129 

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 )] 

136 

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) 

146 

147 dependency_run_list.append(run_construct_portfolio_selector) 

148 

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) 

156 

157 # Write used settings to file 

158 gv.settings().write_used_settings() 

159 sys.exit(0) 

160 

161 

162if __name__ == "__main__": 

163 main(sys.argv[1:])