Coverage for moptipy / examples / jssp / plots.py: 11%
236 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-24 08:49 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-24 08:49 +0000
1"""The JSSP-example specific plots."""
3from math import inf
4from statistics import median
5from typing import Any, Callable, Final, Iterable
7from pycommons.io.console import logger
8from pycommons.io.path import Path, directory_path
9from pycommons.types import type_error
11import moptipy.utils.plot_utils as pu
12from moptipy.evaluation.axis_ranger import AxisRanger
13from moptipy.evaluation.base import F_NAME_RAW, F_NAME_SCALED, TIME_UNIT_MILLIS
14from moptipy.evaluation.end_results import EndResult
15from moptipy.evaluation.end_statistics import EndStatistics, from_end_results
16from moptipy.evaluation.plot_end_results import plot_end_results
17from moptipy.evaluation.plot_end_statistics_over_parameter import (
18 plot_end_statistics_over_param,
19)
20from moptipy.evaluation.plot_progress import plot_progress
21from moptipy.evaluation.progress import Progress
22from moptipy.evaluation.progress import from_logs as pr_from_logs
23from moptipy.evaluation.stat_run import STAT_MEAN_ARITH, StatRun
24from moptipy.evaluation.stat_run import from_progress as sr_from_progress
25from moptipy.examples.jssp.plot_gantt_chart import plot_gantt_chart
26from moptipy.utils.lang import Lang
27from moptipy.utils.plot_defaults import importance_to_font_size
30def plot_end_makespans(end_results: Iterable[EndResult],
31 name_base: str,
32 dest_dir: str,
33 instance_sort_key: Callable = lambda x: x,
34 algorithm_sort_key: Callable = lambda x: x,
35 algorithm_namer: Callable[[str], str] = lambda x: x,
36 x_label_location: float = 1.0) \
37 -> list[Path]:
38 """
39 Plot a set of end result boxes/violins functions into one chart.
41 :param end_results: the iterable of end results
42 :param name_base: the basic name
43 :param dest_dir: the destination directory
44 :param instance_sort_key: the sort key function for instances
45 :param algorithm_sort_key: the sort key function for algorithms
46 :param algorithm_namer: the name function for algorithms receives an
47 algorithm ID and returns an instance name; default=identity function
48 :param x_label_location: the location of the label of the x-axis
49 :returns: the list of generated files
50 """
51 logger(f"beginning to plot chart {name_base}.")
52 if not isinstance(end_results, Iterable):
53 raise type_error(end_results, "end_results", Iterable)
54 if not isinstance(name_base, str):
55 raise type_error(name_base, "name_base", str)
56 if not isinstance(dest_dir, str):
57 raise type_error(dest_dir, "dest_dir", str)
58 if not callable(instance_sort_key):
59 raise type_error(instance_sort_key, "instance_sort_key", call=True)
60 if not callable(algorithm_sort_key):
61 raise type_error(algorithm_sort_key, "algorithm_sort_key", call=True)
63 algorithms: set[str] = set()
64 instances: set[str] = set()
65 pairs: set[str] = set()
66 for er in end_results:
67 algorithms.add(er.algorithm)
68 instances.add(er.instance)
69 pairs.add(f"{er.algorithm}+{er.instance}")
71 n_algos: Final[int] = len(algorithms)
72 n_insts: Final[int] = len(instances)
73 n_pairs: Final[int] = len(pairs)
74 if n_pairs != (n_algos * n_insts):
75 raise ValueError(
76 f"found {n_algos} algorithms and {n_insts} instances, "
77 f"but only {n_pairs} algorithm-instance pairs!")
79 if n_algos >= 16:
80 raise ValueError(f"{n_algos} are just too many algorithms...")
81 max_insts: Final[int] = 16 // n_algos
82 insts: Final[list[str]] = sorted(instances, key=instance_sort_key)
83 result: Final[list[Path]] = []
85 for lang in Lang.all_langs():
86 lang.set_current()
87 figure, plots = pu.create_figure_with_subplots(
88 items=n_insts, max_items_per_plot=max_insts, max_rows=5,
89 max_cols=1, max_width=8.6, default_height_per_row=2.5)
91 for plot, start_inst, end_inst, _, _, _ in plots:
92 instances.clear()
93 instances.update(insts[start_inst:end_inst])
94 plot_end_results(
95 end_results=[er for er in end_results
96 if er.instance in instances],
97 figure=plot,
98 dimension=F_NAME_SCALED,
99 instance_sort_key=instance_sort_key,
100 algorithm_sort_key=algorithm_sort_key,
101 y_label_location=1.0,
102 x_label_location=x_label_location,
103 algorithm_namer=algorithm_namer)
105 result.extend(pu.save_figure(fig=figure,
106 file_name=lang.filename(name_base),
107 dir_name=dest_dir))
108 del figure
110 logger(f"finished plotting chart {name_base}.")
111 result.sort()
112 return result
115def plot_stat_gantt_charts(
116 end_results: Iterable[EndResult],
117 results_dir: str,
118 name_base: str,
119 dest_dir: str,
120 instance_sort_key: Callable = lambda x: x,
121 statistic: Callable[[Iterable[int | float]],
122 int | float] = median) -> list[Path]:
123 """
124 Plot a set of Gantt charts following a specific statistics.
126 :param end_results: the iterable of end results
127 :param results_dir: the result directory
128 :param name_base: the basic name
129 :param dest_dir: the destination directory
130 :param instance_sort_key: the sort key function for instances
131 :param statistic: the statistic to use
132 :returns: the list of generated files
133 """
134 logger(f"beginning to plot stat chart {name_base}.")
135 if not isinstance(end_results, Iterable):
136 raise type_error(end_results, "end_results", Iterable)
137 if not isinstance(results_dir, str):
138 raise type_error(results_dir, "results_dir", str)
139 if not isinstance(name_base, str):
140 raise type_error(name_base, "name_base", str)
141 if not isinstance(dest_dir, str):
142 raise type_error(dest_dir, "dest_dir", str)
143 if not callable(instance_sort_key):
144 raise type_error(instance_sort_key, "instance_sort_key", call=True)
145 if not callable(statistic):
146 raise type_error(statistic, "statistic", call=True)
148 results: Final[list[Path]] = [] # the list of generated files
150 # gather all the data
151 data: Final[dict[str, list[EndResult]]] = {}
152 algorithm: str | None = None
153 for er in end_results:
154 if algorithm is None:
155 algorithm = er.algorithm
156 elif algorithm != er.algorithm:
157 raise ValueError(
158 f"found two algorithms: {algorithm} and {er.algorithm}!")
159 if er.instance in data:
160 data[er.instance].append(er)
161 else:
162 data[er.instance] = [er]
163 if algorithm is None:
164 raise ValueError("Did not encounter any algorithm?")
165 instances: Final[list[str]] = sorted(data.keys(), key=instance_sort_key)
166 del end_results
168 # get the median runs
169 stat_runs: list[Path] = []
170 results_dir = directory_path(results_dir)
171 for instance in instances:
172 runs: list[EndResult] = data[instance]
173 runs.sort()
174 med = statistic([er.best_f for er in runs])
175 best: int | float = inf
176 solution: EndResult | None = None
177 for er in runs:
178 current = abs(er.best_f - med)
179 if current < best:
180 best = current
181 solution = er
182 if solution is None:
183 raise ValueError(
184 f"found no {name_base} end result for instance {instance}.")
185 path: Path = solution.path_to_file(results_dir)
186 path.enforce_file()
187 stat_runs.append(path)
188 del data
189 del instances
190 if len(stat_runs) < 0:
191 raise ValueError("empty set of runs?")
193 # plot the gantt charts
194 for lang in Lang.all_langs():
195 lang.set_current()
197 figure, plots = pu.create_figure_with_subplots(
198 items=len(stat_runs), max_items_per_plot=1, max_cols=2,
199 max_rows=4, max_width=8.6, max_height=11.5)
201 for plot, start, end, _, _, _ in plots:
202 if start != (end - 1):
203 raise ValueError(f"{start} != {end} - 1")
205 args: dict[str, Any] = {
206 "gantt": stat_runs[start],
207 "figure": plot,
208 }
209 if statistic is min:
210 args["markers"] = None
211 else:
212 args["info"] = lambda gantt: \
213 Lang.current().format_str("gantt_info_short", gantt=gantt)
214 if len(plots) > 2:
215 args["importance_to_font_size_func"] = lambda i: \
216 0.9 * importance_to_font_size(i)
218 plot_gantt_chart(**args)
220 results.extend(pu.save_figure(fig=figure,
221 file_name=lang.filename(name_base),
222 dir_name=dest_dir))
224 logger("done plotting stat gantt charts.")
225 return results
228def plot_progresses(results_dir: str,
229 algorithms: Iterable[str],
230 name_base: str,
231 dest_dir: str,
232 time_unit: str = TIME_UNIT_MILLIS,
233 log_time: bool = True,
234 instance_sort_key: Callable = lambda x: x,
235 algorithm_sort_key: Callable = lambda x: x,
236 x_label_location: float = 0.0,
237 include_runs: bool = False,
238 algorithm_namer: Callable[[str], str] = lambda x: x) \
239 -> list[Path]:
240 """
241 Plot a set of end result boxes/violins functions into one chart.
243 :param results_dir: the directory with the log files
244 :param algorithms: the set of algorithms to plot together
245 :param name_base: the basic name
246 :param dest_dir: the destination directory
247 :param time_unit: the time unit to plot
248 :param log_time: should the time axis be scaled logarithmically?
249 :param instance_sort_key: the sort key function for instances
250 :param algorithm_sort_key: the sort key function for algorithms
251 :param x_label_location: the location of the x-labels
252 :param include_runs: should we include the pure runs as well?
253 :param algorithm_namer: the name function for algorithms receives an
254 algorithm ID and returns an instance name; default=identity function
255 :returns: the list of generated files
256 """
257 logger(f"beginning to plot chart {name_base}.")
258 if not isinstance(results_dir, str):
259 raise type_error(results_dir, "results_dir", str)
260 if not isinstance(algorithms, Iterable):
261 raise type_error(algorithms, "algorithms", Iterable)
262 if not isinstance(name_base, str):
263 raise type_error(name_base, "name_base", str)
264 if not isinstance(dest_dir, str):
265 raise type_error(dest_dir, "dest_dir", str)
266 if not isinstance(time_unit, str):
267 raise type_error(time_unit, "time_unit", str)
268 if not isinstance(log_time, bool):
269 raise type_error(log_time, "log_time", bool)
270 if not callable(instance_sort_key):
271 raise type_error(instance_sort_key, "instance_sort_key", call=True)
272 if not callable(algorithm_sort_key):
273 raise type_error(algorithm_sort_key, "algorithm_sort_key", call=True)
274 if not isinstance(x_label_location, float):
275 raise type_error(x_label_location, "x_label_location", float)
276 if not isinstance(include_runs, bool):
277 raise type_error(include_runs, "include_runs", bool)
278 if not callable(algorithm_namer):
279 raise type_error(algorithm_namer, "algorithm_namer", call=True)
281 # get the data
282 spath: Final[Path] = directory_path(results_dir)
283 progresses: Final[list[Progress]] = []
284 for algorithm in sorted(algorithms, key=algorithm_sort_key):
285 progresses.extend(pr_from_logs(spath.resolve_inside(
286 algorithm), time_unit=time_unit, f_name=F_NAME_RAW))
287 if len(progresses) <= 0:
288 raise ValueError(f"did not find log files in dir {results_dir!r}.")
290 stat_runs: Final[list[Progress | StatRun]] = []
291 sr_from_progress(progresses, STAT_MEAN_ARITH,
292 stat_runs.append, False, False)
293 if len(stat_runs) <= 0:
294 raise ValueError(
295 f"failed to compile stat runs from dir {results_dir!r}.")
296 if include_runs:
297 stat_runs.extend(progresses)
298 del progresses
299 instances: Final[list[str]] = sorted({sr.instance for sr in stat_runs},
300 key=instance_sort_key)
301 if len(instances) <= 0:
302 raise ValueError(f"no instances in dir {results_dir!r}.")
303 algos: Final[list[str]] = sorted({sr.algorithm for sr in stat_runs},
304 key=algorithm_sort_key)
305 if len(set(algorithms).difference(algos)) > 0:
306 raise ValueError(
307 f"found the {len(algos)} algorithms {algos}, but expected "
308 f"algorithms {algorithms}.")
310 results: Final[list[Path]] = [] # the list of generated files
312 # plot the progress charts
313 for lang in Lang.all_langs():
314 lang.set_current()
315 figure, plots = pu.create_figure_with_subplots(
316 items=len(instances), max_items_per_plot=1, max_cols=2,
317 max_rows=4, max_width=8.6, max_height=11.5)
319 for plot, start, end, _, _, _ in plots:
320 if start != (end - 1):
321 raise ValueError(f"{start} != {end} - 1")
322 inst = instances[start]
323 plot_progress(
324 progresses=[sr for sr in stat_runs if sr.instance == inst],
325 figure=plot,
326 x_axis=AxisRanger.for_axis_func(log_scale=log_time),
327 importance_to_font_size_func=lambda i:
328 0.9 * importance_to_font_size(i),
329 algorithm_sort_key=algorithm_sort_key,
330 instance_sort_key=instance_sort_key,
331 x_label_location=x_label_location,
332 algorithm_namer=algorithm_namer)
333 axes = pu.get_axes(plot)
334 pu.label_box(axes, inst, x=0.5, y=1)
336 results.extend(pu.save_figure(fig=figure,
337 file_name=lang.filename(name_base),
338 dir_name=dest_dir))
340 logger(f"finished plotting chart {name_base!r}.")
341 return results
344def plot_end_makespans_over_param(
345 end_results: Iterable[EndResult],
346 name_base: str,
347 dest_dir: str,
348 x_getter: Callable[[EndStatistics], int | float],
349 algorithm_getter: Callable[[EndStatistics], str | None] =
350 lambda es: es.algorithm,
351 instance_sort_key: Callable = lambda x: x,
352 algorithm_sort_key: Callable = lambda x: x,
353 title: str | None = None,
354 x_axis: AxisRanger | Callable[[], AxisRanger] = AxisRanger,
355 x_label: str | None = None,
356 x_label_location: float = 1.0,
357 plot_single_instances: bool = True,
358 plot_instance_summary: bool = True,
359 legend_pos: str = "upper right",
360 title_x: float = 0.5,
361 y_label_location: float = 1.0) \
362 -> list[Path]:
363 """
364 Plot the performance over a parameter.
366 :param end_results: the iterable of end results
367 :param name_base: the basic name
368 :param dest_dir: the destination directory
369 :param title: the optional title
370 :param x_getter: the function computing the x-value for each statistics
371 object
372 :param algorithm_getter: the algorithm getter
373 :param instance_sort_key: the sort key function for instances
374 :param algorithm_sort_key: the sort key function for algorithms
375 :param x_axis: the x_axis ranger
376 :param x_label: the x-label
377 :param x_label_location: the location of the x-labels
378 :param plot_single_instances: shall we plot the values for each single
379 instance?
380 :param plot_instance_summary: shall we plot the value over all instances?
381 :param legend_pos: the legend position
382 :param title_x: the title position
383 :param y_label_location: the location of the y label
384 :returns: the list of generated files
385 """
386 logger(f"beginning to plot chart {name_base}.")
387 if not isinstance(end_results, Iterable):
388 raise type_error(end_results, "end_results", Iterable)
389 if not isinstance(name_base, str):
390 raise type_error(name_base, "name_base", str)
391 if not isinstance(dest_dir, str):
392 raise type_error(dest_dir, "dest_dir", str)
393 if not callable(x_getter):
394 raise type_error(x_getter, "x_getter", call=True)
395 if not callable(algorithm_getter):
396 raise type_error(algorithm_getter, "algorithm_getter", call=True)
397 if not callable(instance_sort_key):
398 raise type_error(instance_sort_key, "instance_sort_key", call=True)
399 if not callable(algorithm_sort_key):
400 raise type_error(algorithm_sort_key, "algorithm_sort_key", call=True)
401 if not isinstance(x_label_location, float):
402 raise type_error(x_label_location, "x_label_location", float)
403 if not isinstance(y_label_location, float):
404 raise type_error(y_label_location, "y_label_location", float)
405 if (title is not None) and (not isinstance(title, str)):
406 raise type_error(title, "title", (str, None))
407 if not isinstance(plot_single_instances, bool):
408 raise type_error(plot_single_instances, "plot_single_instances", bool)
409 if not isinstance(plot_instance_summary, bool):
410 raise type_error(plot_instance_summary, "plot_instance_summary", bool)
411 if not (plot_single_instances or plot_instance_summary):
412 raise ValueError("plot_instance_summary and plot_single_instances "
413 "cannot both be False")
414 if not isinstance(legend_pos, str):
415 raise type_error(legend_pos, "legend_pos", str)
416 if not isinstance(title_x, float):
417 raise type_error(title_x, "title_x", float)
419 logger(f"now plotting end statistics over parameter {title!r}.")
421 end_stats: Final[list[EndStatistics]] = []
422 if plot_single_instances:
423 end_stats.extend(from_end_results(end_results))
424 if plot_instance_summary:
425 end_stats.extend(from_end_results(
426 end_results, join_all_instances=True))
427 if len(end_stats) <= 0:
428 raise ValueError("no end statistics records to plot!")
429 result: list[Path] = []
431 for lang in Lang.all_langs():
432 lang.set_current()
433 figure = pu.create_figure(width=5.5)
435 axes = plot_end_statistics_over_param(
436 data=end_stats, figure=figure,
437 algorithm_getter=algorithm_getter,
438 x_axis=x_axis,
439 x_getter=x_getter,
440 x_label=x_label,
441 x_label_location=x_label_location,
442 algorithm_sort_key=algorithm_sort_key,
443 instance_sort_key=instance_sort_key,
444 legend_pos=legend_pos,
445 y_label_location=y_label_location)
446 if title is not None:
447 pu.label_box(axes, title, x=title_x, y=1)
449 result.extend(pu.save_figure(fig=figure,
450 file_name=lang.filename(name_base),
451 dir_name=dest_dir))
453 logger(f"done plotting end statistics over parameter {title!r}.")
454 return result