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
« 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
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
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)
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}"
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.
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
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
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)
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.
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())
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.
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")
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")
184 args: Final[argparse.Namespace] = parser.parse_args()
185 make_per_instance_best_csv(args.source, args.dest, args.instance_key)