Coverage for moptipyapps / prodsched / mfc_generator.py: 89%

173 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2025-12-30 03:25 +0000

1""" 

2Methods for generating MFC instances. 

3 

4In the module :mod:`~moptipyapps.prodsched.instance`, we provide the class 

5:class:`~moptipyapps.prodsched.instance.Instance`. Objects of this class 

6represent a fully deterministic production scheduling scenario. They prescribe 

7demands (:class:`~moptipyapps.prodsched.instance.Demand`) arriving at fixed 

8points in time in the system and work stations that need fixed amounts of 

9work times per product during certain time periods. 

10This allows us to create fully reproducible simulations 

11(:mod:`~moptipyapps.prodsched.simulation`) that show what a factory would do 

12to satisfy the demands. 

13 

14But where does such instance data come from? 

15 

16In the existing research on material flow control, no such fixed instances 

17exist. We invented them. Instead, the existing research [1] uses fixed numbers 

18of products and machines, fixed routes of products through machines, and 

19random distributions to generate demands and work times. 

20 

21So we create the function :func:`~default_stations` that creates the standard 

22work time distributions for the standard work stations. We also create the 

23function :func:`~default_products` that creates the default distributions 

24for the default products. 

25 

26The function :func:`sample_mfc_instance` then creates a material flow instance 

27following these distributions based on a given random seed. 

28This allows us to create scenarios that follow the same structure and random 

29distributions as prescribed in the paper [1] by Thürer et al. 

30However, our instances are fully deterministic. 

31 

32Once could not create a certain number of such instances and average 

33performance metrics over simulations on them. This would likely yield metrics 

34of reasonable accuracy, while allowing us to reproduce, analyze, and trace 

35every single production decision if need be. 

36 

37>>> from moptipyapps.utils.sampling import Gamma 

38>>> inst = sample_mfc_instance([ 

39... Product(0, (0, 1), Gamma.from_alpha_beta(3, 0.26))], [ 

40... Station(0, Gamma.from_k_and_mean(3, 10)), 

41... Station(1, Gamma.from_k_and_mean(2, 10))], 

42... time_end_measure=100, seed=123) 

43 

44>>> inst.name 

45'mfc_1_2_100_0x7b' 

46 

47>>> inst.n_demands 

487 

49 

50>>> inst.demands 

51(Demand(arrival=5.213885878801001, deadline=5.213885878801001, demand_id=0,\ 

52 customer_id=0, product_id=0, amount=1, measure=False),\ 

53 Demand(arrival=25.872387132411287, deadline=25.872387132411287, demand_id=1,\ 

54 customer_id=1, product_id=0, amount=1, measure=False),\ 

55 Demand(arrival=43.062182155666896, deadline=43.062182155666896, demand_id=2,\ 

56 customer_id=2, product_id=0, amount=1, measure=True),\ 

57 Demand(arrival=49.817978344678004, deadline=49.817978344678004, demand_id=3,\ 

58 customer_id=3, product_id=0, amount=1, measure=True),\ 

59 Demand(arrival=58.21166922638016, deadline=58.21166922638016, demand_id=4,\ 

60 customer_id=4, product_id=0, amount=1, measure=True),\ 

61 Demand(arrival=69.09054693162531, deadline=69.09054693162531, demand_id=5,\ 

62 customer_id=5, product_id=0, amount=1, measure=True),\ 

63 Demand(arrival=88.804579148131, deadline=88.804579148131, demand_id=6,\ 

64 customer_id=6, product_id=0, amount=1, measure=True)) 

65 

66>>> len(inst.station_product_unit_times[0][0]) 

671600 

68 

69>>> len(inst.station_product_unit_times[1][0]) 

701600 

71 

72>>> inst.time_end_measure 

73100.0 

74 

75>>> inst.time_end_warmup 

7630.0 

77 

78>>> d = dict(inst.infos) 

79>>> del d["info_generated_on"] 

80>>> del d["info_generator_version"] 

81>>> d 

82{'info_generator': 'moptipyapps.prodsched.mfc_generator',\ 

83 'info_rand_seed_src': 'USER_PROVIDED',\ 

84 'info_rand_seed': '0x7b',\ 

85 'info_time_end_measure_src': 'USER_PROVIDED',\ 

86 'info_time_end_measure': '100',\ 

87 'info_time_end_warmup_src': 'SAMPLED',\ 

88 'info_time_end_warmup': '30',\ 

89 'info_name_src': 'SAMPLED',\ 

90 'info_product_interarrival_times[0]':\ 

91 'Erlang(k=3, theta=3.846153846153846)',\ 

92 'info_product_route[0]': 'USER_PROVIDED',\ 

93 'info_station_processing_time[0]':\ 

94 'Erlang(k=3, theta=3.3333333333333335)',\ 

95 'info_station_processing_time_window_length[0]': 'Const(v=0.125)',\ 

96 'info_station_processing_time[1]': 'Erlang(k=2, theta=5)',\ 

97 'info_station_processing_time_window_length[1]': 'Const(v=0.125)'} 

98 

99>>> inst = sample_mfc_instance(seed=23445) 

100>>> inst.name 

101'mfc_10_13_10000_0x5b95' 

102 

103>>> inst.n_demands 

1049922 

105 

106>>> len([dem for dem in inst.demands if dem.product_id == 0]) 

107959 

108 

109>>> len([dem for dem in inst.demands if dem.product_id == 1]) 

1101055 

111 

112>>> [len(k[0]) for k in inst.station_product_unit_times] 

113[160000, 160000, 0, 160000, 0, 0, 0, 0, 160000, 160000, 160000, 0, 0] 

114 

1151. Matthias Thürer, Nuno O. Fernandes, Hermann Lödding, and Mark Stevenson. 

116 Material Flow Control in Make-to-Stock Production Systems: An Assessment of 

117 Order Generation, Order Release and Production Authorization by Simulation 

118 Flexible Services and Manufacturing Journal. 37(1):1-37. March 2025. 

119 doi:<https://doi.org/10.1007/s10696-024-09532-2> 

120""" 

121import datetime 

122from dataclasses import dataclass 

123from typing import Callable, Final, Iterable 

124 

125from moptipy.utils.nputils import ( 

126 rand_generator, 

127 rand_seed_generate, 

128) 

129from moptipy.utils.strings import sanitize_name 

130from numpy.random import Generator 

131from pycommons.math.int_math import try_int 

132from pycommons.strings.string_conv import num_to_str 

133from pycommons.types import check_int_range, type_error 

134 

135from moptipyapps.prodsched.instance import ( 

136 KEY_IDX_END, 

137 KEY_IDX_START, 

138 MAX_VALUE, 

139 Demand, 

140 Instance, 

141) 

142from moptipyapps.utils.sampling import ( 

143 AtLeast, 

144 Const, 

145 Distribution, 

146 Erlang, 

147 Gamma, 

148 Uniform, 

149) 

150from moptipyapps.version import __version__ 

151 

152#: the "now" function 

153__DTN: Final[Callable[[], datetime.datetime]] = datetime.datetime.now 

154 

155 

156#: The information key for interarrival times 

157INFO_PRODUCT_INTERARRIVAL_TIME_DIST: Final[str] = \ 

158 "info_product_interarrival_times" 

159 

160#: The generator key 

161INFO_GENERATOR: Final[str] = "info_generator" 

162#: The generator version 

163INFO_GENERATOR_VERSION: Final[str] = "info_generator_version" 

164#: When was the instance generated? 

165INFO_GENERATED_ON: Final[str] = "info_generated_on" 

166#: The information key for interarrival times 

167INFO_PRODUCT_ROUTE: Final[str] = "info_product_route" 

168#: a fixed structure 

169INFO_USER_PROVIDED: Final[str] = "USER_PROVIDED" 

170#: a sampled structure 

171INFO_SAMPLED: Final[str] = "SAMPLED" 

172#: The information key for the processing time distribution 

173INFO_STATION_PROCESSING_TIME: Final[str] = "info_station_processing_time" 

174#: The information key for the processing time window length distribution 

175INFO_STATION_PROCESSING_WINDOW_LENGTH: Final[str] = \ 

176 "info_station_processing_time_window_length" 

177#: the random seed 

178INFO_RAND_SEED: Final[str] = "info_rand_seed" 

179#: the random seed source 

180INFO_RAND_SEED_SRC: Final[str] = f"{INFO_RAND_SEED}_src" 

181#: the name source 

182INFO_NAME_SRC: Final[str] = "info_name_src" 

183#: the warmup time end 

184INFO_TIME_END_WARMUP: Final[str] = "info_time_end_warmup" 

185#: the source of the warmup time end 

186INFO_TIME_END_WARMUP_SRC: Final[str] = f"{INFO_TIME_END_WARMUP}_src" 

187#: the measurement time end 

188INFO_TIME_END_MEASURE: Final[str] = "info_time_end_measure" 

189#: the source of the measurement time end 

190INFO_TIME_END_MEASURE_SRC: Final[str] = f"{INFO_TIME_END_MEASURE}_src" 

191 

192 

193@dataclass(order=True, frozen=True) 

194class Product: 

195 """The product sampling definition.""" 

196 

197 #: the product ID 

198 product_id: int 

199 #: the routing of the product 

200 routing: tuple[int, ...] 

201 #: the interarrival distribution 

202 interarrival_times: Distribution 

203 

204 def __init__(self, product_id: int, routing: Iterable[int], 

205 interarrival_times: int | float | Distribution) -> None: 

206 """ 

207 Create the product sampling instruction. 

208 

209 :param product_id: the product id 

210 :param routing: the routing information 

211 :param interarrival_times: the interarrival time distribution 

212 """ 

213 object.__setattr__(self, "product_id", check_int_range( 

214 product_id, "product_id", 0, 1_000_000)) 

215 route: tuple[int, ...] = tuple(routing) 

216 n_route: int = tuple.__len__(route) 

217 if n_route <= 0: 

218 raise ValueError("Route cannot be empty!") 

219 for k in route: 

220 check_int_range(k, "station", 0, 1_000_000) 

221 object.__setattr__(self, "routing", route) 

222 object.__setattr__( 

223 self, "interarrival_times", AtLeast.greater_than_zero( 

224 interarrival_times).simplify()) 

225 

226 def log_info(self, infos: dict[str, str]) -> None: 

227 """ 

228 Log the sampling information of this product to the infos `dict`. 

229 

230 :param infos: the information dictionary 

231 """ 

232 key: Final[str] = f"{KEY_IDX_START}{self.product_id}{KEY_IDX_END}" 

233 infos[f"{INFO_PRODUCT_INTERARRIVAL_TIME_DIST}{key}"] = repr( 

234 self.interarrival_times) 

235 infos[f"{INFO_PRODUCT_ROUTE}{key}"] = INFO_USER_PROVIDED 

236 

237 

238@dataclass(order=True, frozen=True) 

239class Station: 

240 """The station sampling definition.""" 

241 

242 #: the product ID 

243 station_id: int 

244 #: the processing time distribution 

245 processing_time: Distribution 

246 #: the processing window distribution 

247 processing_windows: Distribution 

248 

249 def __init__( 

250 self, station_id: int, 

251 processing_time: int | float | Distribution, 

252 processing_windows: int | float | Distribution | None = None) \ 

253 -> None: 

254 """ 

255 Create the station sampling instruction. 

256 

257 :param station_id: the station id 

258 :param processing_time: the processing time distribution 

259 :param processing_windows: the processing time window length 

260 distribution 

261 """ 

262 object.__setattr__(self, "station_id", check_int_range( 

263 station_id, "station_id", 0, 1_000_000)) 

264 object.__setattr__( 

265 self, "processing_time", 

266 AtLeast.greater_than_zero(processing_time).simplify()) 

267 

268 if processing_windows is None: 

269 processing_windows = Const(1 / 8) 

270 object.__setattr__( 

271 self, "processing_windows", 

272 AtLeast.greater_than_zero(processing_windows).simplify()) 

273 

274 def log_info(self, infos: dict[str, str]) -> None: 

275 """ 

276 Log the sampling information of this product to the infos `dict`. 

277 

278 :param infos: the information dictionary 

279 """ 

280 key: Final[str] = f"{KEY_IDX_START}{self.station_id}{KEY_IDX_END}" 

281 infos[f"{INFO_STATION_PROCESSING_TIME}{key}"] = repr( 

282 self.processing_time) 

283 infos[f"{INFO_STATION_PROCESSING_WINDOW_LENGTH}{key}"] = repr( 

284 self.processing_windows) 

285 

286 

287# pylint: disable=R0914,R0912,R0915 

288def sample_mfc_instance(products: Iterable[Product] | None = None, 

289 stations: Iterable[Station] | None = None, 

290 time_end_warmup: int | float | None = None, 

291 time_end_measure: int | float | None = None, 

292 name: str | None = None, 

293 seed: int | None = None) -> Instance: 

294 """ 

295 Sample an MFC instance. 

296 

297 :param products: the products 

298 :param stations: the work stations 

299 :param time_end_warmup: the end of the warmup period 

300 :param time_end_measure: the end of the measurement period 

301 :param name: the instance name 

302 :param seed: the random seed, if any 

303 :return: the instance 

304 """ 

305 generator: str = str(__file__) 

306 idx: int = str.rfind(generator, "moptipyapps") 

307 if idx >= 0: 

308 generator = str.removesuffix(generator[idx:].replace("/", "."), ".py") 

309 else: 

310 generator = "mfc_generator" 

311 infos: Final[dict[str, str]] = { 

312 INFO_GENERATOR: generator, 

313 INFO_GENERATOR_VERSION: __version__, 

314 INFO_GENERATED_ON: str(__DTN()), 

315 } 

316 

317 if products is None: 

318 products = default_products() 

319 products = sorted(products) 

320 n_products: Final[int] = list.__len__(products) 

321 if n_products <= 0: 

322 raise ValueError(f"Cannot have {n_products} products.") 

323 

324 ids: Final[set[int]] = set() 

325 used_stations: Final[set[int]] = set() 

326 for product in products: 

327 if not isinstance(product, Product): 

328 raise type_error(product, "product", Product) 

329 ids.add(product.product_id) 

330 used_stations.update(product.routing) 

331 if (set.__len__(ids) != n_products) or ( 

332 max(ids) - min(ids) + 1 != n_products): 

333 raise ValueError("Inconsistent product ids.") 

334 

335 n_stations: Final[int] = set.__len__(used_stations) 

336 if not 0 < n_stations < 1_000_000: 

337 raise ValueError(f"Invalid number {n_stations} of stations.") 

338 if stations is None: 

339 if n_stations == 13: 

340 stations = default_stations() 

341 else: 

342 raise ValueError( 

343 "Can only use default settings with 13 stations, " 

344 f"but got {n_stations}.") 

345 

346 stations = sorted(stations) 

347 n_stations_real: int = list.__len__(stations) 

348 if n_stations_real != n_stations: 

349 raise ValueError( 

350 f"Products use {n_stations} stations," 

351 f" but {n_stations_real} are provided.") 

352 

353 ids.clear() 

354 for station in stations: 

355 if not isinstance(station, Station): 

356 raise type_error(station, "station", Station) 

357 ids.add(station.station_id) 

358 min_id: int = min(ids) 

359 max_id: int = max(ids) 

360 if (set.__len__(ids) != n_stations) or ( 

361 max_id - min_id + 1 != n_stations): 

362 raise ValueError("Inconsistent station ids.") 

363 if ids != used_stations: 

364 raise ValueError( 

365 f"Station ids are {min_id}...{max_id}, but products use " 

366 f"stations {sorted(used_stations)}.") 

367 

368 if seed is None: 

369 infos[INFO_RAND_SEED_SRC] = INFO_SAMPLED 

370 seed = rand_seed_generate() 

371 else: 

372 infos[INFO_RAND_SEED_SRC] = INFO_USER_PROVIDED 

373 if not isinstance(seed, int): 

374 raise type_error(seed, "seed", int) 

375 infos[INFO_RAND_SEED] = hex(seed) 

376 

377 if time_end_measure is None: 

378 time_end_measure = 10_000 if (time_end_warmup is None) or ( 

379 time_end_warmup <= 0) else max( 

380 time_end_warmup + 1, (10 * time_end_warmup) / 3) 

381 infos[INFO_TIME_END_MEASURE_SRC] = INFO_SAMPLED 

382 else: 

383 infos[INFO_TIME_END_MEASURE_SRC] = INFO_USER_PROVIDED 

384 time_end_measure = try_int(time_end_measure) 

385 if not 0 < time_end_measure < MAX_VALUE: 

386 raise ValueError( 

387 f"Invalid time_end_measure={time_end_measure}.") 

388 infos[INFO_TIME_END_MEASURE] = num_to_str(time_end_measure) 

389 

390 if time_end_warmup is None: 

391 time_end_warmup = (3 * time_end_measure) / 10 

392 infos[INFO_TIME_END_WARMUP_SRC] = INFO_SAMPLED 

393 else: 

394 infos[INFO_TIME_END_WARMUP_SRC] = INFO_USER_PROVIDED 

395 time_end_warmup = try_int(time_end_warmup) 

396 if not 0 <= time_end_warmup < time_end_measure: 

397 raise ValueError(f"Invalid time_end_warmup={time_end_warmup} " 

398 f"for time_end_measure={time_end_measure}.") 

399 infos[INFO_TIME_END_WARMUP] = num_to_str(time_end_warmup) 

400 

401 if name is None: 

402 infos[INFO_NAME_SRC] = INFO_SAMPLED 

403 s: str = num_to_str(time_end_measure).replace(".", "d") 

404 name = f"mfc_{n_products}_{n_stations}_{s}_{seed:#x}" 

405 else: 

406 infos[INFO_NAME_SRC] = INFO_USER_PROVIDED 

407 uname: str = sanitize_name(name) 

408 if uname != name: 

409 raise ValueError(f"Invalid name {name!r}.") 

410 

411 random: Final[Generator] = rand_generator(seed) 

412 

413 # sample the demands 

414 demands: Final[list[Demand]] = [] 

415 current_id: int = 0 

416 for product in products: 

417 time: float = 0.0 

418 while True: 

419 until = product.interarrival_times.sample(random) 

420 time += until 

421 if time >= time_end_measure: 

422 break 

423 demands.append(Demand( 

424 arrival=time, deadline=time, demand_id=current_id, 

425 customer_id=current_id, product_id=product.product_id, 

426 amount=1, measure=time_end_warmup <= time)) 

427 current_id += 1 

428 

429 #: sample the working times 

430 production_times: Final[list[list[list[float]]]] = [] 

431 for station in stations: 

432 times: list[float] = [] 

433 time = 0.0 

434 while True: 

435 processing = station.processing_time.sample(random) 

436 window = station.processing_windows.sample(random) 

437 time += window 

438 times.extend((processing, time)) 

439 if time >= time_end_measure: 

440 break 

441 production_times.append([ 

442 times if station.station_id in product.routing else [] 

443 for product in products]) 

444 

445 # log the information 

446 for product in products: 

447 product.log_info(infos) 

448 for station in stations: 

449 station.log_info(infos) 

450 

451 return Instance( 

452 name=name, n_products=n_products, 

453 n_customers=current_id, n_stations=n_stations, 

454 n_demands=current_id, 

455 time_end_warmup=time_end_warmup, time_end_measure=time_end_measure, 

456 routes=(product.routing for product in products), 

457 demands=demands, warehous_at_t0=[0] * n_products, 

458 station_product_unit_times=production_times, 

459 infos=infos) 

460 

461 

462def __s1t0(s: Iterable[int]) -> tuple[int, ...]: 

463 """ 

464 Convert stations from 1 to 0-based index. 

465 

466 :param s: the stations 

467 :return: the index 

468 

469 >>> __s1t0((1, 2, 3)) 

470 (0, 1, 2) 

471 """ 

472 return tuple(x - 1 for x in s) 

473 

474 

475def default_products() -> tuple[Product, ...]: 

476 """ 

477 Create the default product sequence as used in [1]. 

478 

479 :return: the default product sequence 

480 

481 >>> default_products() 

482 (Product(product_id=0, routing=(0, 1, 3, 1, 8, 9, 10), \ 

483interarrival_times=Erlang(k=3, theta=3.3333333333333335)), \ 

484Product(product_id=1, routing=(0, 1, 4, 1, 7, 8, 9, 10), \ 

485interarrival_times=Erlang(k=2, theta=5)), \ 

486Product(product_id=2, routing=(0, 1, 5, 3, 1, 8, 11, 10), \ 

487interarrival_times=Uniform(low=5, high=15)), \ 

488Product(product_id=3, routing=(0, 1, 6, 3, 1, 8, 9, 10), \ 

489interarrival_times=Erlang(k=3, theta=3.3333333333333335)), \ 

490Product(product_id=4, routing=(0, 1, 3, 11, 1, 8, 1, 12), \ 

491interarrival_times=Erlang(k=4, theta=2.5)), \ 

492Product(product_id=5, routing=(0, 1, 4, 11, 1, 8, 6, 12), \ 

493interarrival_times=Erlang(k=2, theta=5)), \ 

494Product(product_id=6, routing=(0, 1, 5, 11, 1, 7, 1, 12), \ 

495interarrival_times=Erlang(k=4, theta=2.5)), \ 

496Product(product_id=7, routing=(0, 1, 2, 6, 3, 11, 1, 7, 5, 8, 1, 12), \ 

497interarrival_times=Uniform(low=5, high=15)), \ 

498Product(product_id=8, routing=(0, 1, 2, 4, 3, 5, 11, 1, 7, 1, 9, 5, 12), \ 

499interarrival_times=Erlang(k=4, theta=2.5)), \ 

500Product(product_id=9, routing=(0, 1, 2, 5, 1, 3, 11, 6, 1, 8, 10, 4, 12), \ 

501interarrival_times=Erlang(k=2, theta=5))) 

502 

503 1. Matthias Thürer, Nuno O. Fernandes, Hermann Lödding, and Mark 

504 Stevenson. Material Flow Control in Make-to-Stock Production Systems: 

505 An Assessment of Order Generation, Order Release and Production 

506 Authorization by Simulation Flexible Services and Manufacturing 

507 Journal. 37(1):1-37. March 2025. 

508 doi: https://doi.org/10.1007/s10696-024-09532-2 

509 """ 

510 return ( 

511 Product(0, __s1t0((1, 2, 4, 2, 9, 10, 11)), 

512 Erlang.from_k_and_mean(3, 10)), 

513 Product(1, __s1t0((1, 2, 5, 2, 8, 9, 10, 11)), 

514 Erlang.from_k_and_mean(2, 10)), 

515 Product(2, __s1t0((1, 2, 6, 4, 2, 9, 12, 11)), 

516 Uniform(5, 15)), 

517 Product(3, __s1t0((1, 2, 7, 4, 2, 9, 10, 11)), 

518 Erlang.from_k_and_mean(3, 10)), 

519 Product(4, __s1t0((1, 2, 4, 12, 2, 9, 2, 13)), 

520 Erlang.from_k_and_mean(4, 10)), 

521 Product(5, __s1t0((1, 2, 5, 12, 2, 9, 7, 13)), 

522 Erlang.from_k_and_mean(2, 10)), 

523 Product(6, __s1t0((1, 2, 6, 12, 2, 8, 2, 13)), 

524 Erlang.from_k_and_mean(4, 10)), 

525 Product(7, __s1t0((1, 2, 3, 7, 4, 12, 2, 8, 6, 9, 2, 13)), 

526 Uniform(5, 15)), 

527 Product(8, __s1t0((1, 2, 3, 5, 4, 6, 12, 2, 8, 2, 10, 6, 13)), 

528 Erlang.from_k_and_mean(4, 10)), 

529 Product(9, __s1t0((1, 2, 3, 6, 2, 4, 12, 7, 2, 9, 11, 5, 13)), 

530 Erlang.from_k_and_mean(2, 10))) 

531 

532 

533def default_stations() -> tuple[Station, ...]: 

534 """ 

535 Create the default station sequence as used in [1]. 

536 

537 :return: the default product station 

538 

539 >>> default_stations() 

540 (Station(station_id=0, processing_time=Erlang(k=3, theta=0.26),\ 

541 processing_windows=Const(v=0.125)),\ 

542 Station(station_id=1, processing_time=Erlang(k=3, theta=0.12),\ 

543 processing_windows=Const(v=0.125)),\ 

544 Station(station_id=2, processing_time=Erlang(k=2, theta=1.33),\ 

545 processing_windows=Const(v=0.125)),\ 

546 Station(station_id=3,\ 

547 processing_time=AtLeast(lb=5e-324, d=Exponential(eta=1)),\ 

548 processing_windows=Const(v=0.125)),\ 

549 Station(station_id=4, processing_time=Erlang(k=3, theta=0.67),\ 

550 processing_windows=Const(v=0.125)),\ 

551 Station(station_id=5, processing_time=Erlang(k=4, theta=0.35),\ 

552 processing_windows=Const(v=0.125)),\ 

553 Station(station_id=6, processing_time=Erlang(k=3, theta=0.59),\ 

554 processing_windows=Const(v=0.125)),\ 

555 Station(station_id=7, processing_time=Erlang(k=3, theta=0.63),\ 

556 processing_windows=Const(v=0.125)),\ 

557 Station(station_id=8, processing_time=Erlang(k=2, theta=0.59),\ 

558 processing_windows=Const(v=0.125)),\ 

559 Station(station_id=9, processing_time=Erlang(k=3, theta=0.6),\ 

560 processing_windows=Const(v=0.125)),\ 

561 Station(station_id=10,\ 

562 processing_time=AtLeast(lb=5e-324, d=Exponential(eta=1)),\ 

563 processing_windows=Const(v=0.125)),\ 

564 Station(station_id=11, processing_time=Erlang(k=4, theta=0.29),\ 

565 processing_windows=Const(v=0.125)),\ 

566 Station(station_id=12, processing_time=Erlang(k=3, theta=0.48),\ 

567 processing_windows=Const(v=0.125))) 

568 

569 1. Matthias Thürer, Nuno O. Fernandes, Hermann Lödding, and Mark 

570 Stevenson. Material Flow Control in Make-to-Stock Production Systems: 

571 An Assessment of Order Generation, Order Release and Production 

572 Authorization by Simulation Flexible Services and Manufacturing 

573 Journal. 37(1):1-37. March 2025. 

574 doi: https://doi.org/10.1007/s10696-024-09532-2 

575 """ 

576 return ( 

577 Station(0, Gamma(3, 0.26)), 

578 Station(1, Gamma(3, 0.12)), 

579 Station(2, Gamma(2, 1.33)), 

580 Station(3, Gamma(1, 1.06)), 

581 Station(4, Gamma(3, 0.67)), 

582 Station(5, Gamma(4, 0.35)), 

583 Station(6, Gamma(3, 0.59)), 

584 Station(7, Gamma(3, 0.63)), 

585 Station(8, Gamma(2, 0.59)), 

586 Station(9, Gamma(3, 0.6)), 

587 Station(10, Gamma(1, 1.44)), 

588 Station(11, Gamma(4, 0.29)), 

589 Station(12, Gamma(3, 0.48)))