Coverage for moptipy / evaluation / find_instance_best.py: 19%

112 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-07 11:35 +0000

1"""Find the best per-instance files.""" 

2import argparse 

3from contextlib import suppress 

4from os.path import getsize 

5from typing import Final 

6 

7from pycommons.io.console import logger 

8from pycommons.io.csv import COMMENT_START 

9from pycommons.io.path import Path 

10from pycommons.strings.string_conv import num_to_str, str_to_num 

11 

12from moptipy.api.logging import ( 

13 FILE_SUFFIX, 

14 KEY_BEST_F, 

15 KEY_CURRENT_F, 

16 SECTION_FINAL_STATE, 

17 SECTION_RESULT_Y, 

18 SECTION_SETUP, 

19) 

20from moptipy.utils.help import moptipy_argparser 

21from moptipy.utils.logger import ( 

22 KEY_VALUE_SEPARATOR, 

23 SECTION_END, 

24 SECTION_START, 

25) 

26 

27#: the start tag for state 

28__START_STATE: Final[str] = f"{SECTION_START}{SECTION_FINAL_STATE}" 

29#: the start tag for state 

30__END_STATE: Final[str] = f"{SECTION_END}{SECTION_FINAL_STATE}" 

31#: the start tag for setup 

32__START_SETUP: Final[str] = f"{SECTION_START}{SECTION_SETUP}" 

33#: the end tag for setup 

34__END_SETUP: Final[str] = f"{SECTION_END}{SECTION_SETUP}" 

35#: the start tag for end result 

36__START_END_RESULT: Final[str] = f"{SECTION_START}{SECTION_RESULT_Y}" 

37#: the end tag for end result 

38__END_END_RESULT: Final[str] = f"{SECTION_END}{SECTION_RESULT_Y}" 

39 

40 

41def __find_best(path: Path, inst_key: str, 

42 best: dict[str, tuple[int | float, Path]]) -> None: 

43 """ 

44 Recursively find the best per-instance files. 

45 

46 :param path: the current path 

47 :param inst_key: the key of the instance 

48 :param best: the dictionary with the best results 

49 """ 

50 if not path.exists(): 

51 return 

52 if path.is_dir(): 

53 logger(f"Entering directory {path!r}.") 

54 for f in path.list_dir(): 

55 __find_best(f, inst_key, best) 

56 return 

57 if (not (path.is_file() and path.endswith(FILE_SUFFIX))) or ( 

58 getsize(path) <= 0): 

59 return 

60 

61 state: int = 0 

62 result: int = 0 

63 setup: int = 0 

64 best_f: int | float | None = None 

65 instance: str | None = None 

66 with path.open_for_read() as stream: 

67 for oline in stream: 

68 line = str.strip(oline) 

69 if line.startswith(COMMENT_START): 

70 continue 

71 if line.startswith(__START_STATE): 

72 if (state != 0) or (result == 1) or (setup == 1): 

73 return 

74 state = 1 

75 elif line.startswith(__END_STATE): 

76 if state != 1: 

77 return 

78 state = 2 

79 if (result == 2) and (setup == 2): 

80 break 

81 elif line.startswith(__START_END_RESULT): 

82 if (result != 0) or (state == 1) or (setup == 1): 

83 return 

84 result = 1 

85 elif line.startswith(__END_END_RESULT): 

86 if result != 1: 

87 return 

88 result = 2 

89 if (state == 2) and (setup == 2): 

90 break 

91 elif line.startswith(__START_SETUP): 

92 if (setup != 0) or (state == 1) or (result == 1): 

93 return 

94 setup = 1 

95 elif line.startswith(__END_SETUP): 

96 if setup != 1: 

97 return 

98 setup = 2 

99 if (state == 2) and (result == 2): 

100 break 

101 elif (state == 1) and line.startswith(KEY_BEST_F): 

102 if best_f is not None: 

103 continue 

104 text = line.split(KEY_VALUE_SEPARATOR) 

105 if list.__len__(text) != 2: 

106 continue 

107 with suppress(ValueError): 

108 best_f = str_to_num(text[1]) 

109 elif (state == 1) and line.startswith(KEY_CURRENT_F): 

110 text = line.split(KEY_VALUE_SEPARATOR) 

111 if list.__len__(text) != 2: 

112 continue 

113 with suppress(ValueError): 

114 best_f = str_to_num(text[1]) 

115 elif (setup == 1) and line.startswith(inst_key): 

116 text = line.split(KEY_VALUE_SEPARATOR) 

117 if list.__len__(text) != 2: 

118 continue 

119 instance = str.strip(text[1]) 

120 if str.__len__(instance) <= 0: 

121 instance = None 

122 

123 if (best_f is not None) and (instance is not None) and (result == 2) and ( 

124 setup == 2) and (state == 2) and ((instance not in best) or ( 

125 best_f < best[instance][0])): 

126 best[instance] = (best_f, path) 

127 

128 

129def find_per_instance_best(path: str, inst_key: str) -> list[tuple[ 

130 str, int | float, Path]]: 

131 """ 

132 Find the best results per instance. 

133 

134 :param path: the path in which to search 

135 :param inst_key: the key of the instance, must appear in the 

136 SETUP section 

137 :return: the best results 

138 """ 

139 inst_key = str.strip(inst_key) 

140 if str.__len__(inst_key) <= 0: 

141 raise ValueError("Invalid instance key.") 

142 best: Final[dict[str, tuple[int | float, Path]]] = {} 

143 __find_best(Path(path), inst_key, best) 

144 return sorted((inst, v[0], v[1]) for inst, v in best.items()) 

145 

146 

147def make_per_instance_best_csv(source: str, dest: str, inst_key: str) -> None: 

148 """ 

149 Create a CSV file with the best results per instance. 

150 

151 :param source: the source file 

152 :param dest: the destination file 

153 :param inst_key: the key for finding the instance, must appear in the 

154 SETUP section 

155 """ 

156 result: Final[list[tuple[str, int | float, Path]]] = ( 

157 find_per_instance_best(source, inst_key)) 

158 if list.__len__(result) <= 0: 

159 raise ValueError("Nothing found.") 

160 with Path(dest).open_for_write() as stream: 

161 stream.write("inst;f;path\n") 

162 for inst, f, path in result: 

163 stream.write(f"{inst};{num_to_str(f)};{path}\n") 

164 

165 

166# Run to parse all log files and to create csv 

167if __name__ == "__main__": 

168 parser: Final[argparse.ArgumentParser] = moptipy_argparser( 

169 __file__, 

170 "Make best CSV", 

171 ("Create a CSV file with the paths to the files with the" 

172 "best results on a per-instance basis.")) 

173 parser.add_argument( 

174 "source", nargs="?", default="./results", 

175 help="the location of the experimental results, i.e., the root folder " 

176 "under which to search for log files", type=Path) 

177 parser.add_argument( 

178 "dest", help="the path to the CSV file to be created", 

179 type=Path, nargs="?", default="./evaluation/best.txt") 

180 parser.add_argument( 

181 "instance_key", help="the key for the instance", 

182 type=str, nargs="?", default="f.i.name") 

183 

184 args: Final[argparse.Namespace] = parser.parse_args() 

185 make_per_instance_best_csv(args.source, args.dest, args.instance_key)