Coverage for moptipy / examples / jssp / gantt.py: 84%

64 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-11-24 08:49 +0000

1"""A class for representing Gantt charts as objects.""" 

2from typing import Final 

3 

4import numpy as np 

5from pycommons.io.path import Path 

6from pycommons.types import type_error 

7 

8from moptipy.api.logging import SECTION_RESULT_Y, SECTION_SETUP 

9from moptipy.evaluation.log_parser import LogParser 

10from moptipy.examples.jssp.instance import Instance 

11 

12 

13# start book 

14class Gantt(np.ndarray): 

15 """ 

16 A class representing Gantt charts. 

17 

18 A Gantt chart is a diagram that visualizes when a job on a given 

19 machine begins or ends. We here represent it as a three-dimensional 

20 matrix. This matrix has one row for each machine and one column for 

21 each operation on the machine. 

22 In each cell, it holds three values: the job ID, the start, and the 

23 end time of the job on the machine. The Gantt chart has the 

24 additional attribute `instance` which references the JSSP instance 

25 for which the chart is constructed. 

26 Gantt charts must only be created by an instance of 

27 :class:`moptipy.examples.jssp.gantt_space.GanttSpace`. 

28 """ 

29 

30# end book 

31 

32 #: the JSSP instance for which the Gantt chart is created 

33 instance: Instance 

34 

35 def __new__(cls, space) -> "Gantt": 

36 """ 

37 Create the Gantt chart. 

38 

39 :param space: the Gantt space for which the instance is created. 

40 """ 

41 gnt: Final[Gantt] = np.ndarray.__new__(Gantt, space.shape, space.dtype) 

42 #: the JSSP instance for which the Gantt chart is created 

43 gnt.instance = space.instance 

44 return gnt 

45 

46 @staticmethod 

47 def from_log(file: str, 

48 instance: Instance | None = None) -> "Gantt": 

49 """ 

50 Load a Gantt chart from a log file. 

51 

52 :param file: the log file path 

53 :param instance: the optional JSSP instance: if `None` is provided, 

54 we try to load it from the resources 

55 :returns: the Gantt chart 

56 """ 

57 return _GanttParser(instance).parse_file(file) 

58 

59 

60class _GanttParser(LogParser[Gantt]): 

61 """The log parser for loading Gantt charts.""" 

62 

63 def __init__(self, instance: Instance | None = None): 

64 """ 

65 Create the gantt parser. 

66 

67 :param instance: the optional JSSP instance: if `None` is provided, 

68 we try to load it from the resources 

69 """ 

70 super().__init__() 

71 if (instance is not None) and (not isinstance(instance, Instance)): 

72 raise type_error(instance, "instance", Instance) 

73 #: the internal instance 

74 self.__instance: Instance | None = instance 

75 #: the internal section mode: 0=none, 1=setup, 2=y 

76 self.__sec_mode: int = 0 

77 #: the gantt string 

78 self.__gantt_str: str | None = None 

79 #: the result Gantt chart 

80 self._result: Gantt | None = None 

81 

82 def _start_section(self, title: str) -> bool: 

83 """Start a section.""" 

84 super()._start_section(title) 

85 self.__sec_mode = 0 

86 if title == SECTION_SETUP: 

87 if self.__instance is None: 

88 self.__sec_mode = 1 

89 return True 

90 return False 

91 if title == SECTION_RESULT_Y: 

92 self.__sec_mode = 2 

93 return True 

94 return False 

95 

96 def _lines(self, lines: list[str]) -> bool: 

97 """Parse the lines.""" 

98 if self.__sec_mode == 1: 

99 if self.__instance is not None: 

100 raise ValueError( 

101 f"instance is already set to {self.__instance}.") 

102 key: Final[str] = "y.inst.name: " 

103 for line in lines: 

104 if line.startswith(key): 

105 self.__instance = Instance.from_resource( 

106 line[len(key):].strip()) 

107 if self.__instance is None: 

108 raise ValueError(f"Did not find instance key {key!r} " 

109 f"in section {SECTION_SETUP}!") 

110 elif self.__sec_mode == 2: 

111 self.__gantt_str = " ".join(lines).strip() 

112 else: 

113 raise ValueError("Should not be in section?") 

114 return (self.__instance is None) or (self.__gantt_str is None) 

115 

116 def _parse_file(self, file: Path) -> Gantt: 

117 """End the file.""" 

118 super()._parse_file(file) 

119 if self.__gantt_str is None: 

120 raise ValueError(f"Section {SECTION_RESULT_Y} missing!") 

121 if self.__instance is None: 

122 raise ValueError(f"Section {SECTION_SETUP} missing or empty!") 

123 if self._result is not None: 

124 raise ValueError("Applied parser to more than one log file?") 

125 # pylint: disable=C0415,R0401 

126 from moptipy.examples.jssp.gantt_space import ( # noqa: PLC0415 

127 GanttSpace, # pylint: disable=C0415,R0401 

128 ) 

129 return GanttSpace(self.__instance).from_str(self.__gantt_str) 

130 

131 def _end_parse_file(self, file: Path) -> None: 

132 """Cleanup.""" 

133 self.__gantt_str = None 

134 super()._end_parse_file(file)