Coverage for sparkle/CLI/add_solver.py: 83%
88 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"""Sparkle command to add a solver to the Sparkle platform."""
3import os
4import stat
5import sys
6import argparse
7import shutil
8from pathlib import Path
10from sparkle.platform import file_help as sfh
11from sparkle.CLI.help import global_variables as gv
12from sparkle.structures import PerformanceDataFrame
13from sparkle.solver import Solver, verifiers
14from sparkle.CLI.help import logging as sl
15from sparkle.CLI.initialise import check_for_initialise
16from sparkle.CLI.help import argparse_custom as ac
19def parser_function() -> argparse.ArgumentParser:
20 """Define the command line arguments."""
21 parser = argparse.ArgumentParser(
22 description="Add a solver to the Sparkle platform.")
23 parser.add_argument(*ac.DeterministicArgument.names,
24 **ac.DeterministicArgument.kwargs)
25 parser.add_argument(*ac.SolutionVerifierArgument.names,
26 **ac.SolutionVerifierArgument.kwargs)
27 parser.add_argument(*ac.NicknameSolverArgument.names,
28 **ac.NicknameSolverArgument.kwargs)
29 parser.add_argument(*ac.SolverPathArgument.names, **ac.SolverPathArgument.kwargs)
30 parser.add_argument(*ac.SkipChecksArgument.names, **ac.SkipChecksArgument.kwargs)
31 parser.add_argument(*ac.NoCopyArgument.names, **ac.NoCopyArgument.kwargs)
32 return parser
35def main(argv: list[str]) -> None:
36 """Main function of the command."""
37 # Log command call
38 sl.log_command(sys.argv)
39 check_for_initialise()
41 # Define command line arguments
42 parser = parser_function()
44 # Process command line arguments
45 args = parser.parse_args(argv)
46 solver_source = Path(args.solver_path)
47 deterministic = args.deterministic
48 solution_verifier = args.solution_verifier
50 if not solver_source.exists():
51 print(f'Solver path "{solver_source}" does not exist!')
52 sys.exit(-1)
54 # Make sure it is pointing to the verifiers module
55 if solution_verifier:
56 if Path(solution_verifier).is_file(): # File verifier
57 solution_verifier = (verifiers.SolutionFileVerifier.__name__,
58 solution_verifier)
59 elif solution_verifier not in verifiers.mapping:
60 print(f"ERROR: Unknown solution verifier {solution_verifier}!")
61 sys.exit(-1)
63 nickname = args.nickname
65 if args.run_checks:
66 print("Running checks...")
67 solver = Solver(Path(solver_source))
68 pcs_file = solver.get_pcs_file()
69 if pcs_file is None:
70 print("None or multiple .pcs files found. Solver "
71 "is not valid for configuration.")
72 else:
73 print(f"One pcs file detected: {pcs_file.name}. ", end="")
74 if solver.read_pcs_file():
75 print("Can read the pcs file.")
76 else:
77 print("WARNING: Can not read the provided pcs file format.")
79 configurator_wrapper_path = solver_source / Solver.wrapper
80 if not (configurator_wrapper_path.is_file()
81 and os.access(configurator_wrapper_path, os.X_OK)):
82 print(f"WARNING: Solver {solver_source.name} does not have a solver wrapper "
83 f"(Missing file {Solver.wrapper}) or is not executable. ")
85 # Start add solver
86 solver_directory = gv.settings().DEFAULT_solver_dir / solver_source.name
87 if solver_directory.exists():
88 print(f"ERROR: Solver {solver_source.name} already exists! "
89 "Can not add new solver.")
90 sys.exit(-1)
91 if args.no_copy:
92 print(f"Creating symbolic link from {solver_source} "
93 f"to {solver_directory}...")
94 if not os.access(solver_source, os.W_OK):
95 raise PermissionError("Sparkle does not have the right to write to the "
96 "destination folder.")
97 solver_directory.symlink_to(solver_source.absolute())
98 else:
99 print(f"Copying {solver_source.name} to platform...")
100 solver_directory.mkdir(parents=True)
101 shutil.copytree(solver_source, solver_directory, dirs_exist_ok=True)
103 # Save the deterministic bool in the solver
104 with (solver_directory / Solver.meta_data).open("w+") as fout:
105 fout.write(str({"deterministic": deterministic,
106 "verifier": solution_verifier}))
108 # Add RunSolver executable to the solver
109 runsolver_path = gv.settings().DEFAULT_runsolver_exec
110 if runsolver_path.name in [file.name for file in solver_directory.iterdir()]:
111 print("Warning! RunSolver executable detected in Solver "
112 f"{solver_source.name}. This will be replaced with "
113 f"Sparkle's version of RunSolver. ({runsolver_path})")
114 runsolver_target = solver_directory / runsolver_path.name
115 shutil.copyfile(runsolver_path, runsolver_target)
116 runsolver_target.chmod(os.stat(runsolver_target).st_mode | stat.S_IEXEC)
118 performance_data = PerformanceDataFrame(
119 gv.settings().DEFAULT_performance_data_path,
120 objectives=gv.settings().get_general_sparkle_objectives())
121 performance_data.add_solver(str(solver_directory))
122 performance_data.save_csv()
124 print(f"Adding solver {solver_source.name} done!")
126 if nickname is not None:
127 sfh.add_remove_platform_item(
128 solver_directory,
129 gv.solver_nickname_list_path,
130 gv.file_storage_data_mapping[gv.solver_nickname_list_path],
131 key=nickname)
133 solver = Solver(solver_directory) # Recreate solver from its new directory
134 if solver.get_pcs_file() is not None:
135 print("Generating missing PCS files...")
136 solver.port_pcs("IRACE") # Create PCS file for IRACE
137 print("Generating done!")
139 # Write used settings to file
140 gv.settings().write_used_settings()
141 sys.exit(0)
144if __name__ == "__main__":
145 main(sys.argv[1:])