Coverage for moptipy / examples / jssp / demo_examples.py: 46%
193 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"""Some fixed demo codes for the JSSP examples."""
2import sys
3import urllib.request as rq
4from typing import Callable, Final, Iterable
6import numpy as np
7from matplotlib.figure import Figure # type: ignore
8from pycommons.io.console import logger
9from pycommons.io.path import UTF8, Path, write_lines
10from pycommons.types import type_error
12from moptipy.examples.jssp.experiment import INSTANCES
13from moptipy.examples.jssp.gantt import Gantt
14from moptipy.examples.jssp.gantt_space import GanttSpace
15from moptipy.examples.jssp.instance import (
16 Instance,
17 compute_makespan_lower_bound,
18)
19from moptipy.examples.jssp.ob_encoding import OperationBasedEncoding
20from moptipy.examples.jssp.plot_gantt_chart import (
21 marker_lb,
22 marker_makespan,
23 plot_gantt_chart,
24)
25from moptipy.spaces.permutations import Permutations
26from moptipy.utils.lang import Lang
27from moptipy.utils.plot_utils import cm_to_inch, create_figure, save_figure
30def demo_instance() -> Instance:
31 """
32 Get the demo instance we use.
34 :return: the demo instance
35 """
36 attr: Final[str] = "_res_"
37 if not hasattr(demo_instance, attr):
38 setattr(demo_instance, attr, Instance.from_resource("demo"))
39 return getattr(demo_instance, attr)
42def demo_search_space() -> Permutations:
43 """
44 Obtain an instance of the demo search space.
46 :return: the demo search space
47 """
48 attr: Final[str] = "_res_"
49 if not hasattr(demo_search_space, attr):
50 instance: Final[Instance] = demo_instance()
51 setattr(demo_search_space, attr,
52 Permutations.with_repetitions(instance.jobs,
53 instance.machines))
54 return getattr(demo_search_space, attr)
57def demo_point_in_search_space(optimum: bool = False) -> np.ndarray:
58 """
59 Create a demo point in the search space.
61 :param optimum: should we return the optimal solution?
62 :return: the point
63 """
64 space: Final[Permutations] = demo_search_space()
65 res = space.create()
66 if optimum:
67 np.copyto(res, [0, 1, 2, 3, 1, 0, 1, 2, 0, 1,
68 3, 2, 2, 3, 1, 3, 0, 2, 3, 0])
69 else:
70 np.copyto(res, [0, 2, 3, 2, 2, 3, 1, 1, 0, 3,
71 1, 3, 2, 1, 3, 2, 0, 1, 0, 0])
72 space.validate(res)
73 return res
76def demo_solution_space() -> GanttSpace:
77 """
78 Obtain an instance of the demo solution space.
80 :return: the demo solution space
81 """
82 attr: Final[str] = "_res_"
83 if not hasattr(demo_solution_space, attr):
84 instance: Final[Instance] = demo_instance()
85 setattr(demo_solution_space, attr, GanttSpace(instance))
86 return getattr(demo_solution_space, attr)
89def demo_encoding() -> OperationBasedEncoding:
90 """
91 Obtain an instance of the demo encoding.
93 :return: the demo encoding
94 """
95 attr: Final[str] = "_res_"
96 if not hasattr(demo_encoding, attr):
97 instance: Final[Instance] = demo_instance()
98 setattr(demo_encoding, attr, OperationBasedEncoding(instance))
99 return getattr(demo_encoding, attr)
102def demo_solution(optimum: bool = False) -> Gantt:
103 """
104 Create a demo solution.
106 :param optimum: should we return the optimal solution?
107 :return: the demo solution
108 """
109 space: Final[GanttSpace] = demo_solution_space()
110 result: Final[Gantt] = space.create()
111 demo_encoding().decode(demo_point_in_search_space(optimum=optimum),
112 result)
113 space.validate(result)
114 return result
117def __make_gantt_demo_name(optimum: bool,
118 with_makespan: bool,
119 with_lower_bound: bool) -> str:
120 """
121 Construct the name for the demo gantt chart.
123 :param optimum: should we return the optimal solution?
124 :param with_makespan: should the makespan be included?
125 :param with_lower_bound: should the lower bound be included?
126 :return: the file name
127 """
128 prefix: str = "gantt_demo_opt_" if optimum else "gantt_demo_"
129 if with_makespan:
130 if with_lower_bound:
131 return prefix + "with_makespan_and_lb"
132 return prefix + "with_makespan"
133 if with_lower_bound:
134 return prefix + "with_lb"
135 return prefix + "without_makespan"
138def demo_gantt_chart(dirname: str,
139 optimum: bool = False,
140 with_makespan: bool = False,
141 with_lower_bound: bool = False,
142 width: float | int | None = cm_to_inch(10),
143 height: float | int | None = None,
144 filename: str | Callable =
145 __make_gantt_demo_name) -> list[Path]:
146 """
147 Plot the demo gantt chart.
149 :param dirname: the directory
150 :param optimum: should we return the optimal solution?
151 :param with_makespan: should the makespan be included?
152 :param with_lower_bound: should the lower bound be included?
153 :param width: the optional width
154 :param height: the optional height
155 :param filename: the file name
156 """
157 the_dir: Final[Path] = Path(dirname)
158 the_dir.ensure_dir_exists()
160 if callable(filename):
161 filename = filename(optimum,
162 with_makespan,
163 with_lower_bound)
164 if not isinstance(filename, str):
165 raise type_error(filename, "filename", str)
167 result: Final[list[Path]] = []
168 markers: list[Callable] = []
170 if with_makespan:
171 def info(g: Gantt) -> str:
172 return Lang.current().format_str("gantt_info", gantt=g)
173 if not optimum:
174 markers.append(marker_makespan)
175 else:
176 def info(g: Gantt) -> str:
177 return Lang.current().format_str("gantt_info_no_ms", gantt=g)
179 if with_lower_bound:
180 markers.append(marker_lb)
182 for lang in Lang.all_langs():
183 lang.set_current()
184 figure: Figure = create_figure(width=width, height=height)
186 gantt = demo_solution(optimum=optimum)
187 plot_gantt_chart(gantt=gantt,
188 figure=figure,
189 x_label_inside=False,
190 y_label_inside=False,
191 markers=markers,
192 info=info)
194 result.extend(save_figure(fig=figure,
195 dir_name=the_dir,
196 file_name=lang.filename(filename),
197 formats="svg"))
198 return result
201def makespan_lower_bound_table(
202 dirname: str,
203 filename: str = "makespan_lower_bound",
204 instances: Iterable[str] =
205 tuple(["demo"] + list(INSTANCES))) -> Path: # noqa
206 """
207 Make a table with the makespan lower bounds.
209 Larger lower bounds are taken from the repository
210 https://github.com/thomasWeise/jsspInstancesAndResults.
212 :param dirname: the directory where to store the generated file.
213 :param filename: the filename
214 :param instances: the instances
215 :returns: the generated file
216 """
217 insts = list(instances)
218 insts.sort()
219 i = insts.index("demo")
220 if i >= 0:
221 del insts[i]
222 insts.insert(0, "demo")
224 # get the data with the lower bounds information
225 url: Final[str] = "https://github.com/thomasWeise/jsspInstancesAndResults"
226 logger(f"now loading data from {url!r}.")
227 with rq.urlopen(url) as f: # nosec # noqa
228 data: str = f.read().decode(UTF8).strip()
229 if not data:
230 raise ValueError(f"Could not load data form {url}.")
231 logger(f"finished loading data from {url!r}.")
232 start = data.find("<table>")
233 if start <= 0:
234 raise ValueError(f"Could not find <table> in {data}.")
235 end = data.rfind("</dl>", start)
236 if end <= start:
237 raise ValueError(f"Could not find </dl> in {data}.")
238 data = data[start:end].strip()
240 lang: Final[Lang] = Lang.current()
242 text: Final[list[str]] = [
243 (r"|name|$\jsspJobs$|$\jsspMachines$|$\lowerBound(\objf)$|"
244 r"$\lowerBound(\objf)^{\star}$|source for "
245 r"$\lowerBound(\objf)^{\star}$|"),
246 "|:--|--:|--:|--:|--:|:--|",
247 ]
248 bsrc: Final[str] = "[@eq:jsspLowerBound]"
250 # compute the data
251 for instn in insts:
252 solved_to_optimality: bool = False
253 inst = Instance.from_resource(instn)
254 lb = compute_makespan_lower_bound(machines=inst.machines,
255 jobs=inst.jobs,
256 matrix=inst)
257 src = bsrc
258 if instn == "demo":
259 lbx = lb
260 solved_to_optimality = True
261 else:
262 start = data.find(
263 f'<td align="right"><strong>{instn}</strong></td>')
264 if start <= 0:
265 start = data.find(f'<td align="right">{instn}</td>')
266 if start <= 0:
267 raise ValueError(f"Could not find instance {instn!r}.")
268 prefix = ""
269 suffix = "</td>"
270 solved_to_optimality = True
271 else:
272 prefix = "<strong>"
273 suffix = "</strong></td>"
274 end = data.find("</tr>", start)
275 if end <= start:
276 raise ValueError(
277 f"Could not find end of instance {instn!r}.")
278 sel = data[start:end].strip()
279 logger(f"located instance text {sel!r} for {instn!r}.")
280 prefix = (f'<td align="right">{inst.jobs}</td>\n<td align="'
281 f'right">{inst.machines}</td>\n<td align="right">'
282 f"{prefix}")
283 pi = sel.find(prefix)
284 if pi <= 0:
285 raise ValueError(
286 f"Could not find prefix for instance {instn!r}.")
287 sel = sel[(pi + len(prefix)):]
288 si = sel.find(suffix)
289 if si <= 0:
290 raise ValueError(
291 f"Could not find suffix for instance {instn!r}.")
292 lbx = int(sel[0:si])
293 npt = '<td align="center"><a href="#'
294 npi = sel.find(npt, si)
295 if npi < si:
296 raise ValueError(
297 f"Could not find source start for instance {instn!r}.")
298 sel = sel[npi + len(npt):]
299 end = sel.find('"')
300 if end <= 0:
301 raise ValueError(
302 f"Could not find source end for instance {instn!r}.")
303 srcname = sel[:end].strip()
304 logger(f"Source name is {srcname}.")
306 fsrc = data.find(f'<dt id="user-content-{srcname.lower()}">'
307 f"{srcname}</dt><dd>")
308 if fsrc <= 0:
309 raise ValueError(
310 f"Could not find source mark for instance {instn!r}.")
311 fsrce = data.find("</dd>", fsrc)
312 if fsrce <= fsrc:
313 raise ValueError(
314 f"Could not find end mark for instance {instn!r}.")
315 sel = data[fsrc:fsrce].strip()
316 nm = sel.rfind("</a>")
317 if nm <= 0:
318 raise ValueError(
319 f"Could not find link end for instance {instn!r}"
320 f" and source name {srcname}.")
321 fm = sel.rfind(">", 0, nm)
322 if fm >= nm:
323 raise ValueError(
324 f"Could not find label mark for instance {instn!r}"
325 f" and source name {srcname}.")
326 src = f"[@{sel[(fm + 1):nm].strip()}]"
328 if lbx <= lb:
329 if lbx < lb:
330 raise ValueError(f"Lbx {lbx} cannot be larger than lb {lb}.")
331 if not solved_to_optimality:
332 src = "[@eq:jsspLowerBound]"
333 instname = f"`{instn}`"
334 if not solved_to_optimality:
335 instname = f"**{instname}**"
337 text.append(
338 f"|{instname}|{inst.machines}|{inst.jobs}|{lb}|{lbx}|{src}|")
340 # write the result
341 out_dir = Path(dirname)
342 out_dir.ensure_dir_exists()
343 out_file = out_dir.resolve_inside(lang.filename(filename) + ".md")
344 with out_dir.open_for_write() as wd:
345 write_lines(text, wd)
346 out_file.enforce_file()
347 logger(f"finished writing output results to file {out_file!r}.")
348 return out_file
351# are we being executed?
352if __name__ == "__main__":
353 dest_dir: Final[Path] = Path(sys.argv[1])
354 dest_dir.ensure_dir_exists()
355 logger(f"We will print the JSSP examples into dir {dest_dir!r}.")
356 makespan_lower_bound_table(dest_dir)
357 demo_gantt_chart(dest_dir, True)
358 demo_gantt_chart(dest_dir, False)
359 logger(f"Finished printing the JSSP examples into dir {dest_dir!r}.")