Coverage for sparkle/CLI/initialise.py: 72%

108 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-01-07 15:22 +0000

1#!/usr/bin/env python3 

2"""Command to initialise a Sparkle platform.""" 

3import subprocess 

4import argparse 

5import shutil 

6import os 

7import sys 

8import warnings 

9from pathlib import Path 

10 

11from sparkle.CLI.help import argparse_custom as ac 

12from sparkle.CLI.help import snapshot_help as snh 

13from sparkle.CLI.help import global_variables as gv 

14from sparkle.configurator.implementations.irace import IRACE 

15from sparkle.platform import Settings 

16from sparkle.structures import PerformanceDataFrame, FeatureDataFrame 

17 

18 

19def parser_function() -> argparse.ArgumentParser: 

20 """Parse CLI arguments for the initialise command.""" 

21 parser = argparse.ArgumentParser( 

22 description="Initialise the Sparkle platform in the current directory.") 

23 parser.add_argument(*ac.DownloadExamplesArgument.names, 

24 **ac.DownloadExamplesArgument.kwargs) 

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

26 **ac.NoSavePlatformArgument.kwargs) 

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

28 **ac.RebuildRunsolverArgument.kwargs) 

29 return parser 

30 

31 

32def detect_sparkle_platform_exists(check: callable = all) -> Path: 

33 """Return whether a Sparkle platform is currently active. 

34 

35 The default working directories are checked for existence, for each directory in the 

36 CWD. If any of the parents of the CWD yield true, this path is returned 

37 

38 Args: 

39 check: Method to check if the working directory exists. Defaults to all. 

40 

41 Returns: 

42 Path to the Sparkle platform if it exists, None otherwise. 

43 """ 

44 cwd = Path.cwd() 

45 while str(cwd) != cwd.root: 

46 if check([(cwd / wd).exists() for wd in gv.settings().DEFAULT_working_dirs]): 

47 return cwd 

48 cwd = cwd.parent 

49 return None 

50 

51 

52def initialise_irace() -> None: 

53 """Initialise IRACE.""" 

54 if shutil.which("R") is None: 

55 warnings.warn("R is not installed, which is required for the IRACE" 

56 "configurator. Consider installing R.") 

57 return 

58 print("Initialising IRACE ...") 

59 r6_package_check = subprocess.run(["Rscript", "-e", 

60 'library("R6")'], capture_output=True) 

61 if r6_package_check.returncode != 0: # R6 is not installed 

62 print("Installing R6 package (IRACE dependency) ...") 

63 r6_install = subprocess.run([ 

64 "Rscript", "-e", 

65 f'install.packages("{IRACE.r6_dependency_package.absolute()}",' 

66 f'lib="{IRACE.configurator_path.absolute()}")'], capture_output=True) 

67 if r6_install.returncode != 0: 

68 print("An error occured during the installation of R6:\n", 

69 r6_install.stdout.decode(), "\n", 

70 r6_install.stderr.decode(), "\n" 

71 "IRACE installation failed!") 

72 return 

73 # Install IRACE from tarball 

74 irace_install = subprocess.run( 

75 ["Rscript", "-e", 

76 f'install.packages("{IRACE.configurator_package.absolute()}",' 

77 f'lib="{IRACE.configurator_path.absolute()}")'], capture_output=True) 

78 if irace_install.returncode != 0 or not IRACE.configurator_executable.exists(): 

79 print("An error occured during the installation of IRACE:\n", 

80 irace_install.stdout.decode(), "\n", 

81 irace_install.stderr.decode()) 

82 else: 

83 print("IRACE installed!") 

84 

85 

86def check_for_initialise() -> None: 

87 """Function to check if initialize command was executed and execute it otherwise. 

88 

89 Args: 

90 argv: List of the arguments from the caller. 

91 requirements: The requirements that have to be executed before the calling 

92 function. 

93 """ 

94 platform_path = detect_sparkle_platform_exists() 

95 if platform_path is None: 

96 print("-----------------------------------------------") 

97 print("No Sparkle platform found; " 

98 "The platform will now be initialized automatically.") 

99 print("-----------------------------------------------") 

100 initialise_sparkle() 

101 elif platform_path != Path.cwd(): 

102 print(f"[WARNING] Sparkle platform found in {platform_path} instead of " 

103 f"{Path.cwd()}. Switching to CWD to {platform_path}") 

104 os.chdir(platform_path) 

105 

106 

107def initialise_sparkle(save_existing_platform: bool = True, 

108 download_examples: bool = False, 

109 rebuild_runsolver: bool = False) -> None: 

110 """Initialize a new Sparkle platform. 

111 

112 Args: 

113 save_existing_platform: If present, save the current platform as a snapshot. 

114 download_examples: Downloads examples from the Sparkle Github. 

115 WARNING: May take a some time to complete due to the large amount of data. 

116 rebuild_runsolver: Will clean the RunSolver executable and rebuild it. 

117 """ 

118 print("Start initialising Sparkle platform ...") 

119 if detect_sparkle_platform_exists(check=all): 

120 print("Current Sparkle platform found!") 

121 if save_existing_platform: 

122 print("Saving as snapshot...") 

123 snh.save_current_platform() 

124 snh.remove_current_platform(filter=[gv.settings().DEFAULT_settings_dir]) 

125 print("Your settings directory was not removed.") 

126 

127 for working_dir in gv.settings().DEFAULT_working_dirs: 

128 working_dir.mkdir(exist_ok=True) 

129 

130 # Check if Settings file exists, otherwise initialise a default one 

131 if not Path(Settings.DEFAULT_settings_path).exists(): 

132 print("Settings file does not exist, initializing default settings ...") 

133 gv.__settings = Settings(Settings.DEFAULT_example_settings_path) 

134 gv.settings().write_settings_ini(Path(Settings.DEFAULT_settings_path)) 

135 

136 # Initialise latest scenario file 

137 gv.ReportingScenario.DEFAULT_reporting_scenario_path.open("w+") 

138 

139 # Initialise the FeatureDataFrame 

140 FeatureDataFrame(gv.settings().DEFAULT_feature_data_path) 

141 

142 # Initialise the Performance DF with the static dimensions 

143 # TODO: We have many sparkle settings values regarding ``number of runs'' 

144 # E.g. configurator, parallel portfolio, and here too. Should we unify this more, or 

145 # just make another setting that does this specifically for performance data? 

146 PerformanceDataFrame(gv.settings().DEFAULT_performance_data_path, 

147 objectives=gv.settings().get_general_sparkle_objectives(), 

148 n_runs=1) 

149 

150 if rebuild_runsolver: 

151 print("Cleaning Runsolver ...") 

152 runsolver_clean = subprocess.run(["make", "clean"], 

153 cwd=gv.settings().DEFAULT_runsolver_dir, 

154 capture_output=True) 

155 if runsolver_clean.returncode != 0: 

156 warnings.warn(f"[{runsolver_clean.returncode}] Cleaning of Runsolver failed " 

157 f"with the following msg: {runsolver_clean.stdout.decode()}") 

158 

159 # Check that Runsolver is compiled, otherwise, compile 

160 if not gv.settings().DEFAULT_runsolver_exec.exists(): 

161 print("Runsolver does not exist, trying to compile...") 

162 if not (gv.settings().DEFAULT_runsolver_dir / "Makefile").exists(): 

163 warnings.warn("Runsolver executable doesn't exist and cannot find makefile." 

164 " Please verify the contents of the directory: " 

165 f"{gv.settings().DEFAULT_runsolver_dir}") 

166 else: 

167 compile_runsolver =\ 

168 subprocess.run(["make"], 

169 cwd=gv.settings().DEFAULT_runsolver_dir, 

170 capture_output=True) 

171 if compile_runsolver.returncode != 0: 

172 warnings.warn("Compilation of Runsolver failed with the following msg:" 

173 f"[{compile_runsolver.returncode}] " 

174 f"{compile_runsolver.stderr.decode()}") 

175 else: 

176 print("Runsolver compiled successfully!") 

177 

178 # If Runsolver is compiled, check that it can be executed 

179 if gv.settings().DEFAULT_runsolver_exec.exists(): 

180 runsolver_check = subprocess.run([gv.settings().DEFAULT_runsolver_exec, 

181 "--version"], 

182 capture_output=True) 

183 if runsolver_check.returncode != 0: 

184 print("WARNING: Runsolver executable cannot be run successfully. " 

185 "Please verify the following error messages:\n" 

186 f"{runsolver_check.stderr.decode()}") 

187 

188 # Check that java is available for SMAC2 

189 if shutil.which("java") is None: 

190 # NOTE: An automatic resolution of Java at this point would be good 

191 # However, loading modules from Python has thusfar not been successfull. 

192 warnings.warn("Could not find Java as an executable! Java 1.8.0_402 is required " 

193 "to use SMAC2 as a configurator. Consider installing Java.") 

194 

195 # Check if IRACE is installed 

196 if not IRACE.configurator_executable.exists(): 

197 initialise_irace() 

198 

199 if download_examples: 

200 # Download Sparkle examples from Github 

201 print("Downloading examples ...") 

202 curl = subprocess.Popen( 

203 ["curl", "https://codeload.github.com/ADA-research/Sparkle/tar.gz/main"], 

204 stdout=subprocess.PIPE) 

205 outpath = Path("outfile.tar.gz") 

206 with curl.stdout, outpath.open("wb") as outfile: 

207 tar = subprocess.Popen(["tar", "-xz", "--strip=1", "Sparkle-main/Examples"], 

208 stdin=curl.stdout, stdout=outfile) 

209 curl.wait() # Wait for the download to complete 

210 tar.wait() # Wait for the extraction to complete 

211 outpath.unlink(missing_ok=True) 

212 

213 print("New Sparkle platform initialised!") 

214 

215 

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

217 """Main function of the command.""" 

218 # Define command line arguments 

219 parser = parser_function() 

220 # Process command line arguments 

221 args = parser.parse_args(argv) 

222 initialise_sparkle(save_existing_platform=args.no_save, 

223 download_examples=args.download_examples, 

224 rebuild_runsolver=args.rebuild_runsolver) 

225 sys.exit(0) 

226 

227 

228if __name__ == "__main__": 

229 main(sys.argv[1:])