Coverage for src/sparkle/CLI/check.py: 17%

88 statements  

« prev     ^ index     » next       coverage.py v7.16.0, created at 2026-09-08 12:00 +0000

1"""Command to help users check if their input solvers, datasets etc. are correct.""" 

2 

3import sys 

4import os 

5import argparse 

6 

7from runrunner import Runner 

8 

9from sparkle.solver import Solver 

10from sparkle.instance import Instance_Set, InstanceSet 

11from sparkle.selector.extractor import Extractor 

12from sparkle.platform import Settings 

13from sparkle.types import SolverStatus 

14 

15from sparkle.CLI.help import logging as sl 

16from sparkle.CLI.help import argparse_custom as ac 

17from sparkle.CLI.help import global_variables as gv 

18 

19 

20def parser_function() -> argparse.ArgumentParser: 

21 """Define the command line arguments.""" 

22 parser = argparse.ArgumentParser( 

23 description="Command to help users check if their input solvers, instance sets " 

24 "or feature extractors are readable by Sparkle. Specify a path to " 

25 "an instance to test calling the wrapper." 

26 ) 

27 parser.add_argument(*ac.CheckTypeArgument.names, **ac.CheckTypeArgument.kwargs) 

28 parser.add_argument(*ac.CheckPathArgument.names, **ac.CheckPathArgument.kwargs) 

29 parser.add_argument(*ac.InstancePathOptional.names, **ac.InstancePathOptional.kwargs) 

30 parser.add_argument(*ac.SeedArgument.names, **ac.SeedArgument.kwargs) 

31 # Settings arguments 

32 parser.add_argument( 

33 *Settings.OPTION_solver_cutoff_time.args, 

34 **Settings.OPTION_solver_cutoff_time.kwargs, 

35 ) 

36 return parser 

37 

38 

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

40 """Main entry point of the Check command.""" 

41 parser = parser_function() 

42 args = parser.parse_args(argv) 

43 settings = gv.settings(args) 

44 

45 # Log command call 

46 sl.log_command(sys.argv, settings.random_state) 

47 

48 type_map = { 

49 "extractor": Extractor, 

50 "feature-extractor": Extractor, 

51 "solver": Solver, 

52 "instance-set": Instance_Set, 

53 "Extractor": Extractor, 

54 "Feature-Extractor": Extractor, 

55 "Instance-Set": Instance_Set, 

56 "Solver": Solver, 

57 "FeatureExtractor": Extractor, 

58 "InstanceSet": Instance_Set, 

59 } 

60 if args.type not in type_map: 

61 options_text = "\n".join([f"\t- {value}" for value in type_map.keys()]) 

62 raise ValueError( 

63 f"Unknown type {args.type}. Please choose from:\n{options_text}" 

64 ) 

65 type = type_map[args.type] 

66 print(f"Checking {type.__name__} in directory {args.path} ...") 

67 object = type(args.path) 

68 print("Resolved to:") 

69 print(object.__repr__()) 

70 

71 # Conduct object specific tests 

72 if isinstance(object, Solver): 

73 if object.pcs_file: 

74 print() 

75 print(object.get_configuration_space()) 

76 if not os.access(object.wrapper_file, os.X_OK): 

77 print( 

78 f"Wrapper {object.wrapper_file} is not executable! " 

79 f"Check that wrapper execution rights are set for all." 

80 ) 

81 sys.exit(-1) 

82 if args.instance_path: # Instance to test with 

83 object._runsolver_exec = settings.DEFAULT_runsolver_exec 

84 if False and not object.runsolver_exec.exists(): 

85 print( 

86 f"Runsolver {object.runsolver_exec} not found. " 

87 "Checking without Runsolver currently not supported." 

88 ) 

89 else: 

90 # Test the wrapper with a dummy call 

91 print(f"\nTesting Wrapper {object.wrapper} ...") 

92 # Patchfix runsolver 

93 objectives = settings.objectives if settings.objectives else [] 

94 cutoff = ( 

95 settings.solver_cutoff_time if settings.solver_cutoff_time else 5 

96 ) 

97 configuration = {} 

98 if object.pcs_file: 

99 print("\nSample Configuration:") 

100 sample_conf = object.get_configuration_space().sample_configuration() 

101 print(sample_conf) 

102 configuration = dict(sample_conf) 

103 result = object.run( 

104 instances=args.instance_path, 

105 objectives=objectives, 

106 seed=42, # Dummy seed 

107 cutoff_time=cutoff, 

108 configuration=configuration, 

109 log_dir=sl.caller_log_dir, 

110 run_on=Runner.LOCAL, 

111 ) 

112 log = [ 

113 path 

114 for path in sl.caller_log_dir.iterdir() 

115 if path.suffix == ".rawres" 

116 ] 

117 if len(log) > 0: 

118 print("\nSolver Output:") 

119 print(log[0].open().read()) 

120 else: 

121 print("\nERROR: Solver Log output not found!\n") 

122 print("\nResult:") 

123 for obj in objectives: # Check objectives 

124 if obj.name not in result: 

125 print(f"\tSolver output is missing objective {obj.name}") 

126 else: 

127 print(f"\t{obj.name}: {result[obj.name]}") 

128 status_key = [key for key in result.keys() if key.startswith("status")][ 

129 0 

130 ] 

131 if result[status_key] == SolverStatus.UNKNOWN: 

132 print( 

133 f"Sparkle was unable to process {obj.name} output. " 

134 "Check that your wrapper is able to handle KILL SIGNALS. " 

135 "It is important to always communicate back to Sparkle " 

136 "on regular exit and termination signals for stability." 

137 ) 

138 sys.exit(-1) 

139 elif isinstance(object, Extractor): 

140 if args.instance_path: # Test on an instance 

141 # Patchfix runsolver 

142 object.runsolver_exec = settings.DEFAULT_runsolver_exec 

143 if not object.runsolver_exec.exists(): 

144 print( 

145 f"Runsolver {object.runsolver_exec} not found. " 

146 "Checking without Runsolver currently not supported." 

147 ) 

148 else: 

149 print(f"\nTesting Wrapper {object.wrapper} ...") 

150 # cutoff = args.cutoff_time if args.cutoff_time else 20 # Short call 

151 result = object.run( 

152 instance=args.instance_path, log_dir=sl.caller_log_dir 

153 ) 

154 # Print feature results 

155 print("Feature values:") 

156 for f_group, f_name, f_value in result: 

157 print(f"\t[{f_group}] {f_name}: {f_value}") 

158 else: 

159 print("Feature names:") 

160 for f_group, fname in object.features: 

161 print(f"\t{f_group}: {fname}") 

162 elif isinstance(object, InstanceSet): 

163 print("\nList of instances:") 

164 for i_name, i_path in zip(object.instance_names, object.instance_paths): 

165 print(f"\t{i_name}: {i_path}") 

166 

167 sys.exit(0) 

168 

169 

170if __name__ == "__main__": 

171 main(sys.argv[1:])