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
« 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
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
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
32def detect_sparkle_platform_exists(check: callable = all) -> Path:
33 """Return whether a Sparkle platform is currently active.
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
38 Args:
39 check: Method to check if the working directory exists. Defaults to all.
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
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!")
86def check_for_initialise() -> None:
87 """Function to check if initialize command was executed and execute it otherwise.
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)
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.
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.")
127 for working_dir in gv.settings().DEFAULT_working_dirs:
128 working_dir.mkdir(exist_ok=True)
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))
136 # Initialise latest scenario file
137 gv.ReportingScenario.DEFAULT_reporting_scenario_path.open("w+")
139 # Initialise the FeatureDataFrame
140 FeatureDataFrame(gv.settings().DEFAULT_feature_data_path)
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)
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()}")
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!")
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()}")
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.")
195 # Check if IRACE is installed
196 if not IRACE.configurator_executable.exists():
197 initialise_irace()
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)
213 print("New Sparkle platform initialised!")
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)
228if __name__ == "__main__":
229 main(sys.argv[1:])