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