Coverage for src / sparkle / CLI / initialise.py: 76%
108 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-21 15:31 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-21 15:31 +0000
1#!/usr/bin/env python3
2"""Command to initialise a Sparkle platform."""
4import subprocess
5import argparse
6import shutil
7import os
8import sys
9import warnings
10from pathlib import Path
12from sparkle.CLI.help import argparse_custom as ac
13from sparkle.CLI.help import snapshot_help as snh
15from sparkle.platform import Settings
16from sparkle.CLI.help import global_variables as gv
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 )
24 parser.add_argument(
25 *ac.DownloadExamplesArgument.names, **ac.DownloadExamplesArgument.kwargs
26 )
27 parser.add_argument(
28 *ac.NoSavePlatformArgument.names, **ac.NoSavePlatformArgument.kwargs
29 )
30 parser.add_argument(
31 *ac.RebuildRunsolverArgument.names, **ac.RebuildRunsolverArgument.kwargs
32 )
33 return parser
36def detect_sparkle_platform_exists(check: callable = all) -> Path:
37 """Return whether a Sparkle platform is currently active.
39 The default working directories are checked for existence, for each directory in the
40 CWD. If any of the parents of the CWD yield true, this path is returned
42 Args:
43 check: Method to check if the working directory exists. Defaults to all.
45 Returns:
46 Path to the Sparkle platform if it exists, None otherwise.
47 """
48 cwd = Path.cwd()
49 while str(cwd) != cwd.root:
50 if check([(cwd / wd).exists() for wd in Settings.DEFAULT_working_dirs]):
51 return cwd
52 cwd = cwd.parent
53 return None
56def check_for_initialise() -> None:
57 """Function to check if initialize command was executed and execute it otherwise.
59 Args:
60 argv: List of the arguments from the caller.
61 requirements: The requirements that have to be executed before the calling
62 function.
63 """
64 platform_path = detect_sparkle_platform_exists()
65 if platform_path is None:
66 print("-----------------------------------------------")
67 print(
68 "No Sparkle platform found; "
69 "The platform will now be initialized automatically."
70 )
71 print("-----------------------------------------------")
72 initialise_sparkle()
73 elif platform_path != Path.cwd():
74 print(
75 f"[WARNING] Sparkle platform found in {platform_path} instead of "
76 f"{Path.cwd()}. Switching to CWD to {platform_path}"
77 )
78 os.chdir(platform_path)
81def initialise_sparkle(
82 save_existing_platform: bool = True,
83 interactive: bool = False,
84 download_examples: bool = False,
85 rebuild_runsolver: bool = False,
86) -> None:
87 """Initialize a new Sparkle platform.
89 Args:
90 save_existing_platform: If present, save the current platform as a snapshot.
91 interactive: Ask for user input or not.
92 download_examples: Downloads examples from the Sparkle Github.
93 WARNING: May take a some time to complete due to the large amount of data.
94 rebuild_runsolver: Will clean the RunSolver executable and rebuild it.
95 """
96 print("Start initialising Sparkle platform ...")
97 # NOTE: Import here for speedup
98 from sparkle.configurator.implementations import IRACE, SMAC2, ParamILS
99 from sparkle.structures import PerformanceDataFrame, FeatureDataFrame
101 if detect_sparkle_platform_exists(check=all):
102 print("Current Sparkle platform found!")
103 if save_existing_platform:
104 print("Saving as snapshot...")
105 snh.save_current_platform()
106 snh.remove_current_platform(filter=[Settings.DEFAULT_settings_dir])
107 print("Your settings directory was not removed.")
109 for working_dir in Settings.DEFAULT_working_dirs:
110 working_dir.mkdir(exist_ok=True)
112 # Check if Settings file exists, otherwise initialise a default one
113 if not Path(Settings.DEFAULT_settings_path).exists():
114 print("Settings file does not exist, initializing default settings ...")
115 gv.__settings = Settings(Settings.DEFAULT_example_settings_path)
116 gv.settings().write_settings_ini(Path(Settings.DEFAULT_settings_path))
118 # Initialise the FeatureDataFrame
119 FeatureDataFrame(Settings.DEFAULT_feature_data_path)
121 # Initialise the Performance DF with the static dimensions
122 # TODO: We have many sparkle settings values regarding ``number of runs''
123 # E.g. configurator, parallel portfolio, and here too. Should we unify this more, or
124 # just make another setting that does this specifically for performance data?
125 PerformanceDataFrame(
126 Settings.DEFAULT_performance_data_path,
127 objectives=gv.settings().objectives,
128 n_runs=1,
129 )
131 if rebuild_runsolver:
132 print("Cleaning Runsolver ...")
133 runsolver_clean = subprocess.run(
134 ["make", "clean"], cwd=Settings.DEFAULT_runsolver_dir, capture_output=True
135 )
136 if runsolver_clean.returncode != 0:
137 warnings.warn(
138 f"[{runsolver_clean.returncode}] Cleaning of Runsolver failed "
139 f"with the following msg: {runsolver_clean.stdout.decode()}"
140 )
142 # Check that Runsolver is compiled, otherwise, compile
143 if not Settings.DEFAULT_runsolver_exec.exists():
144 print("Runsolver does not exist, trying to compile...")
145 if not (Settings.DEFAULT_runsolver_dir / "Makefile").exists():
146 warnings.warn(
147 "Runsolver executable doesn't exist and cannot find makefile."
148 " Please verify the contents of the directory: "
149 f"{Settings.DEFAULT_runsolver_dir}"
150 )
151 else:
152 compile_runsolver = subprocess.run(
153 ["make"], cwd=Settings.DEFAULT_runsolver_dir, capture_output=True
154 )
155 if compile_runsolver.returncode != 0:
156 warnings.warn(
157 "Compilation of Runsolver failed with the following msg:"
158 f"[{compile_runsolver.returncode}] "
159 f"{compile_runsolver.stderr.decode()}"
160 )
161 else:
162 print("Runsolver compiled successfully!")
164 # If Runsolver is compiled, check that it can be executed
165 if Settings.DEFAULT_runsolver_exec.exists():
166 runsolver_check = subprocess.run(
167 [Settings.DEFAULT_runsolver_exec, "--version"], capture_output=True
168 )
169 if runsolver_check.returncode != 0:
170 print(
171 "WARNING: Runsolver executable cannot be run successfully. "
172 "Please verify the following error messages:\n"
173 f"{runsolver_check.stderr.decode()}"
174 )
176 # Check that java is available for SMAC2
177 if shutil.which("java") is None:
178 # NOTE: An automatic resolution of Java at this point would be good
179 # However, loading modules from Python has thusfar not been successfull.
180 print(
181 "WARNING: Could not find Java as an executable! Java 1.8.0_402 is required"
182 " to use SMAC2 or ParamILS as a configurator. Consider installing Java."
183 )
185 # Check for each configurator that it is available
186 if interactive and not SMAC2.configurator_executable.exists():
187 print("SMAC2 is not installed, would you like to install? (Y/n) ...")
188 if input().lower() == "y":
189 print("Installing SMAC2 ...")
190 SMAC2.download_requirements()
191 if interactive and not ParamILS.configurator_executable.exists():
192 print("ParamILS is not installed, would you like to install? (Y/n) ...")
193 if input().lower() == "y":
194 print("Installing ParamILS ...")
195 ParamILS.download_requirements()
196 if interactive and not IRACE.check_requirements():
197 if shutil.which("R") is None:
198 print(
199 "R is not installed, which is required for the IRACE "
200 "configurator (installation). Consider installing R."
201 )
202 else:
203 print("IRACE is not installed, would you like to install? (Y/n) ...")
204 if input().lower() == "y":
205 print("Installing IRACE ...")
206 IRACE.download_requirements()
207 irace = IRACE()
208 print(f"Installed IRACE version {irace.version}")
210 if download_examples:
211 # Download Sparkle examples from Github
212 print("Downloading examples ...")
213 curl = subprocess.Popen(
214 ["curl", "https://codeload.github.com/ADA-research/Sparkle/tar.gz/main"],
215 stdout=subprocess.PIPE,
216 )
217 outpath = Path("outfile.tar.gz")
218 with curl.stdout, outpath.open("wb") as outfile:
219 tar = subprocess.Popen(
220 ["tar", "-xz", "--strip=1", "Sparkle-main/Examples"],
221 stdin=curl.stdout,
222 stdout=outfile,
223 )
224 curl.wait() # Wait for the download to complete
225 tar.wait() # Wait for the extraction to complete
226 outpath.unlink(missing_ok=True)
228 print("New Sparkle platform initialised!")
231def main(argv: list[str]) -> None:
232 """Main function of the command."""
233 # Define command line arguments
234 parser = parser_function()
235 # Process command line arguments
236 args = parser.parse_args(argv)
237 initialise_sparkle(
238 save_existing_platform=args.no_save,
239 interactive=True,
240 download_examples=args.download_examples,
241 rebuild_runsolver=args.rebuild_runsolver,
242 )
243 sys.exit(0)
246if __name__ == "__main__":
247 main(sys.argv[1:])