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

101 statements  

« prev     ^ index     » next       coverage.py v7.6.4, created at 2024-11-05 14:48 +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.platform import CommandName 

12from sparkle.CLI.help.argparse_custom import DownloadExamplesArgument 

13from sparkle.CLI.help import snapshot_help as snh 

14from sparkle.platform.settings_objects import Settings 

15from sparkle.structures import PerformanceDataFrame, FeatureDataFrame 

16from sparkle.CLI.help import global_variables as gv 

17from sparkle.configurator.implementations.irace import IRACE 

18 

19 

20def parser_function() -> argparse.ArgumentParser: 

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

22 parser = argparse.ArgumentParser( 

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

24 parser.add_argument(*DownloadExamplesArgument.names, 

25 **DownloadExamplesArgument.kwargs) 

26 return parser 

27 

28 

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

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

31 

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

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

34 

35 Args: 

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

37 

38 Returns: 

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

40 """ 

41 cwd = Path.cwd() 

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

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

44 return cwd 

45 cwd = cwd.parent 

46 return None 

47 

48 

49def initialise_irace() -> None: 

50 """Initialise IRACE.""" 

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

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

53 "configurator. Make sure R is installed and try again.") 

54 return 

55 print("Initialising IRACE ...") 

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

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

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

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

60 r6_install = subprocess.run([ 

61 "Rscript", "-e", 

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

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

64 if r6_install.returncode != 0: 

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

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

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

68 "IRACE installation failed!") 

69 return 

70 else: 

71 print(f"[{r6_package_check.returncode}] " 

72 "R6 package (IRACE dependency) was already installed: " 

73 f"{r6_package_check.stdout.decode()}\n" 

74 f"{r6_package_check.stderr.decode()}") 

75 # Install IRACE from tarball 

76 irace_install = subprocess.run( 

77 ["Rscript", "-e", 

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

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

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

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

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

83 irace_install.stderr.decode()) 

84 else: 

85 print("IRACE installed!") 

86 

87 

88def check_for_initialise(requirements: list[CommandName] = None)\ 

89 -> None: 

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

91 

92 Args: 

93 argv: List of the arguments from the caller. 

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

95 function. 

96 """ 

97 platform_path = detect_sparkle_platform_exists() 

98 if platform_path is None: 

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

100 print("No Sparkle platform found; " 

101 + "The platform will now be initialized automatically") 

102 if requirements is not None: 

103 if len(requirements) == 1: 

104 print(f"The command {requirements[0]} has \ 

105 to be executed before executing this command.") 

106 else: 

107 print(f"""The commands {", ".join(requirements)} \ 

108 have to be executed before executing this command.""") 

109 print("-----------------------------------------------") 

110 initialise_sparkle() 

111 elif platform_path != Path.cwd(): 

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

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

114 os.chdir(platform_path) 

115 

116 

117def initialise_sparkle(download_examples: bool = False) -> None: 

118 """Initialize a new Sparkle platform. 

119 

120 Args: 

121 download_examples: Downloads examples from the Sparkle Github. 

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

123 """ 

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

125 if detect_sparkle_platform_exists(check=all): 

126 print("Current Sparkle platform found! Saving as snapshot.") 

127 snh.save_current_platform() 

128 snh.remove_current_platform() 

129 

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

131 working_dir.mkdir(exist_ok=True) 

132 

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

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

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

136 gv.__settings = Settings(Settings.DEFAULT_example_settings_path) 

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

138 

139 # Initialise latest scenario file 

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

141 

142 # Initialise the FeatureDataFrame 

143 FeatureDataFrame(gv.settings().DEFAULT_feature_data_path) 

144 

145 # Initialise the Performance DF with the static dimensions 

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

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

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

149 PerformanceDataFrame(gv.settings().DEFAULT_performance_data_path, 

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

151 n_runs=1) 

152 

153 # Check that Runsolver is compiled, otherwise, compile 

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

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

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

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

158 " Please verify the contents of the directory: " 

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

160 else: 

161 compile_runsolver =\ 

162 subprocess.run(["make"], 

163 cwd=gv.settings().DEFAULT_runsolver_dir, 

164 capture_output=True) 

165 if compile_runsolver.returncode != 0: 

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

167 f"[{compile_runsolver.returncode}] " 

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

169 else: 

170 print("Runsolver compiled successfully!") 

171 # Check that java is available for SMAC2 

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

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

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

175 warnings.warn("Could not find Java as an executable! " 

176 "Java 1.8.0_402 is required to use SMAC2 as a configurator.") 

177 

178 # Check if IRACE is installed 

179 if not IRACE.configurator_executable.exists(): 

180 initialise_irace() 

181 

182 if download_examples: 

183 # Download Sparkle examples from Github 

184 # NOTE: Needs to be thoroughly tested after Pip install is working 

185 print("Downloading examples ...") 

186 curl = subprocess.Popen( 

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

188 stdout=subprocess.PIPE) 

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

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

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

192 stdin=curl.stdout, stdout=outfile) 

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

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

195 outpath.unlink(missing_ok=True) 

196 

197 print("New Sparkle platform initialised!") 

198 

199 

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

201 """Main function of the command.""" 

202 # Define command line arguments 

203 parser = parser_function() 

204 # Process command line arguments 

205 args = parser.parse_args(argv) 

206 download = False if args.download_examples is None else args.download_examples 

207 initialise_sparkle(download_examples=download) 

208 sys.exit(0) 

209 

210 

211if __name__ == "__main__": 

212 main(sys.argv[1:])