Coverage for moptipy / tests / on_jssp.py: 85%

102 statements  

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

1"""Perform tests on the Job Shop Scheduling Problem.""" 

2 

3from typing import Any, Callable, Final, Iterable, cast 

4 

5from numpy.random import Generator, default_rng 

6from pycommons.types import type_error 

7 

8from moptipy.api.algorithm import Algorithm 

9from moptipy.api.mo_algorithm import MOAlgorithm 

10from moptipy.api.mo_problem import MOProblem 

11from moptipy.api.objective import Objective 

12from moptipy.examples.jssp.gantt import Gantt 

13from moptipy.examples.jssp.gantt_space import GanttSpace 

14from moptipy.examples.jssp.instance import Instance 

15from moptipy.examples.jssp.makespan import Makespan 

16from moptipy.examples.jssp.ob_encoding import OperationBasedEncoding 

17from moptipy.examples.jssp.worktime import Worktime 

18from moptipy.mo.problem.weighted_sum import WeightedSum 

19from moptipy.operators.permutations.op0_shuffle import Op0Shuffle 

20from moptipy.spaces.permutations import Permutations 

21from moptipy.tests.algorithm import validate_algorithm 

22from moptipy.tests.mo_algorithm import validate_mo_algorithm 

23from moptipy.tests.objective import validate_objective 

24 

25 

26def jssp_instances_for_tests() -> Iterable[str]: 

27 """ 

28 Get a sequence of JSSP instances to test on. 

29 

30 :returns: an iterable of JSSP instance names 

31 """ 

32 r = default_rng() 

33 ri = r.integers 

34 insts: list[str] = [ 

35 "demo", "ft06", "ft10", f"abz{ri(5, 10)}", f"dmu{ri(10, 81)}", 

36 f"orb0{ri(1, 10)}", f"swv{ri(10, 21)}", f"ta{ri(10, 65)}", 

37 f"ta{ri(65, 70)}", f"ta{ri(70, 75)}", f"yn{ri(1, 5)}"] 

38 r.shuffle(cast("list", insts)) 

39 return insts 

40 

41 

42def make_gantt_valid(inst: Instance) -> Callable[[Generator, Gantt], Gantt]: 

43 """ 

44 Make a function that creates valid Gantt charts. 

45 

46 :param inst: the JSSP instance 

47 :returns: a function that can make gantt charts valid 

48 """ 

49 pr = Permutations.with_repetitions(inst.jobs, inst.machines) 

50 op0 = Op0Shuffle(pr) 

51 oe = OperationBasedEncoding(inst) 

52 

53 def __make_valid(prnd: Generator, x: Gantt, ppr=pr, 

54 pop0=op0, poe=oe) -> Gantt: 

55 xx = ppr.create() 

56 pop0.op0(prnd, xx) 

57 poe.decode(xx, x) 

58 return x 

59 

60 return __make_valid 

61 

62 

63def validate_algorithm_on_1_jssp( 

64 algorithm: Algorithm | Callable[ 

65 [Instance, Permutations, Objective], Algorithm], 

66 instance: str | None = None, max_fes: int = 100, 

67 required_result: int | None = None, 

68 post: Callable[[Algorithm, int], Any] | None = None) -> None: 

69 """ 

70 Check the validity of a black-box algorithm on the JSSP. 

71 

72 :param algorithm: the algorithm or algorithm factory 

73 :param instance: the instance name, or `None` to randomly pick one 

74 :param max_fes: the maximum number of FEs 

75 :param required_result: the optional required result quality 

76 :param post: a check to run after each execution of the algorithm, 

77 receiving the algorithm and the number of consumed FEs as parameter 

78 """ 

79 if not (isinstance(algorithm, Algorithm) or callable(algorithm)): 

80 raise type_error(algorithm, "algorithm", Algorithm, True) 

81 if instance is None: 

82 instance = str(default_rng().choice(Instance.list_resources())) 

83 if not isinstance(instance, str): 

84 raise type_error(instance, "JSSP instance name", (str, None)) 

85 inst = Instance.from_resource(instance) 

86 if not isinstance(inst, Instance): 

87 raise type_error(inst, f"loaded JSSP instance {instance!r}", Instance) 

88 if (post is not None) and (not callable(post)): 

89 raise type_error(post, "post", None, call=True) 

90 

91 search_space = Permutations.with_repetitions(inst.jobs, 

92 inst.machines) 

93 solution_space = GanttSpace(inst) 

94 encoding = OperationBasedEncoding(inst) 

95 objective = Makespan(inst) 

96 if callable(algorithm): 

97 algorithm = algorithm(inst, search_space, objective) 

98 if not isinstance(algorithm, Algorithm): 

99 raise type_error(algorithm, "algorithm", Algorithm, call=True) 

100 

101 goal: int 

102 if required_result is None: 

103 lb: int = objective.lower_bound() 

104 ub: int = objective.upper_bound() 

105 goal = max(lb + 1, min(ub - 1, int(0.5 + (lb + (0.96 * (ub - lb)))))) 

106 else: 

107 goal = required_result 

108 

109 validate_algorithm(algorithm=algorithm, 

110 solution_space=solution_space, 

111 objective=objective, 

112 search_space=search_space, 

113 encoding=encoding, 

114 max_fes=max_fes, 

115 required_result=goal, 

116 post=post) 

117 

118 

119def validate_algorithm_on_jssp( 

120 algorithm: Callable[[Instance, Permutations, 

121 Objective], Algorithm], 

122 max_fes: int = 100, 

123 post: Callable[[Algorithm, int], Any] | None = None) -> None: 

124 """ 

125 Validate an algorithm on a set of JSSP instances. 

126 

127 :param algorithm: the algorithm factory 

128 :param max_fes: the maximum FEs 

129 :param post: a check to run after each execution of the algorithm, 

130 receiving the algorithm and the number of consumed FEs as parameter 

131 """ 

132 for i in jssp_instances_for_tests(): 

133 validate_algorithm_on_1_jssp(algorithm, i, max_fes=max_fes, post=post) 

134 

135 

136def validate_objective_on_1_jssp( 

137 objective: Objective | Callable[[Instance], Objective], 

138 instance: str | None = None, 

139 is_deterministic: bool = True) -> None: 

140 """ 

141 Validate an objective function on 1 JSSP instance. 

142 

143 :param objective: the objective function or a factory creating it 

144 :param instance: the instance name 

145 :param is_deterministic: is the objective function deterministic? 

146 """ 

147 if instance is None: 

148 instance = str(default_rng().choice(Instance.list_resources())) 

149 if not isinstance(instance, str): 

150 raise type_error(instance, "JSSP instance name", (str, None)) 

151 inst = Instance.from_resource(instance) 

152 if not isinstance(inst, Instance): 

153 raise type_error(inst, f"loaded JSSP instance {instance!r}", Instance) 

154 

155 if callable(objective): 

156 objective = objective(inst) 

157 

158 validate_objective( 

159 objective=objective, 

160 solution_space=GanttSpace(inst), 

161 make_solution_space_element_valid=make_gantt_valid(inst), 

162 is_deterministic=is_deterministic) 

163 

164 

165def validate_objective_on_jssp( 

166 objective: Objective | Callable[[Instance], Objective], 

167 is_deterministic: bool = True) -> None: 

168 """ 

169 Validate an objective function on JSSP instances. 

170 

171 :param objective: the objective function or a factory creating it 

172 :param is_deterministic: is the objective function deterministic? 

173 """ 

174 for i in jssp_instances_for_tests(): 

175 validate_objective_on_1_jssp(objective, i, is_deterministic) 

176 

177 

178def validate_mo_algorithm_on_1_jssp( 

179 algorithm: MOAlgorithm | Callable[ 

180 [Instance, Permutations, MOProblem], MOAlgorithm], 

181 instance: str | None = None, max_fes: int = 100) -> None: 

182 """ 

183 Check the validity of a black-box multi-objective algorithm on the JSSP. 

184 

185 :param algorithm: the algorithm or algorithm factory 

186 :param instance: the instance name, or `None` to randomly pick one 

187 :param max_fes: the maximum number of FEs 

188 """ 

189 if not (isinstance(algorithm, MOAlgorithm) or callable(algorithm)): 

190 raise type_error(algorithm, "algorithm", MOAlgorithm, True) 

191 

192 random: Final[Generator] = default_rng() 

193 if instance is None: 

194 instance = str(random.choice(Instance.list_resources())) 

195 if not isinstance(instance, str): 

196 raise type_error(instance, "JSSP instance name", (str, None)) 

197 inst = Instance.from_resource(instance) 

198 if not isinstance(inst, Instance): 

199 raise type_error(inst, f"loaded JSSP instance '{instance}'", Instance) 

200 

201 search_space = Permutations.with_repetitions(inst.jobs, 

202 inst.machines) 

203 solution_space = GanttSpace(inst) 

204 encoding = OperationBasedEncoding(inst) 

205 

206 weights: Final[list[int | float]] = [float(random.uniform(0.01, 10)), 

207 float(random.uniform(0.01, 10))] \ 

208 if random.integers(2) <= 0 else \ 

209 [1 + int(random.integers(1 << random.integers(40))), 

210 1 + int(random.integers(1 << random.integers(40)))] 

211 problem: Final[MOProblem] = WeightedSum( 

212 [Makespan(inst), Worktime(inst)], weights) 

213 

214 if callable(algorithm): 

215 algorithm = algorithm(inst, search_space, problem) 

216 if not isinstance(algorithm, MOAlgorithm): 

217 raise type_error(algorithm, "algorithm", MOAlgorithm, call=True) 

218 

219 validate_mo_algorithm(algorithm=algorithm, 

220 solution_space=solution_space, 

221 problem=problem, 

222 search_space=search_space, 

223 encoding=encoding, 

224 max_fes=max_fes) 

225 

226 

227def validate_mo_algorithm_on_jssp( 

228 algorithm: Callable[ 

229 [Instance, Permutations, MOProblem], MOAlgorithm]) -> None: 

230 """ 

231 Validate a multi-objective algorithm on a set of JSSP instances. 

232 

233 :param algorithm: the algorithm factory 

234 """ 

235 for i in jssp_instances_for_tests(): 

236 validate_mo_algorithm_on_1_jssp(algorithm, i, 100)