Coverage for moptipyapps / qap / instance.py: 76%

131 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2025-12-11 04:40 +0000

1""" 

2An instance of the Quadratic Assignment Problem. 

3 

4In this module, we provide the class :class:`~Instance` that encapsulates all 

5information of a problem instance of the Quadratic Assignment Problem (QAP). 

6The QAP aims to locate facilities at locations such that the flow-distance 

7product sum combining a flows of goods between instances with the distances of 

8the locations becomes minimal. Each instance therefore presents a matrix with 

9:attr:`~moptipyapps.qap.instance.Instance.distances` and a matrix with flows 

10:attr:`~moptipyapps.qap.instance.Instance.flows`. 

11 

121. Eliane Maria Loiola, Nair Maria Maia de Abreu, Paulo Oswaldo 

13 Boaventura-Netto, Peter Hahn, and Tania Querido. A survey for the 

14 Quadratic Assignment Problem. European Journal of Operational Research. 

15 176(2):657-690. January 2007. https://doi.org/10.1016/j.ejor.2005.09.032. 

162. Rainer E. Burkard, Eranda Çela, Panos M. Pardalos, and 

17 Leonidas S. Pitsoulis. The Quadratic Assignment Problem. In Ding-Zhu Du, 

18 Panos M. Pardalos, eds., Handbook of Combinatorial Optimization, 

19 pages 1713-1809, 1998, Springer New York, NY, USA. 

20 https://doi.org/10.1007/978-1-4613-0303-9_27. 

21 

22 

23We additionally provide access to several standard QAP benchmark instances 

24via the :meth:`~Instance.from_resource` and :meth:`~Instance.list_resources` 

25methods. The standard benchmark instances stem from QAPLIB, a library of QAP 

26instances, which can be found at <https://qaplib.mgi.polymtl.ca> and 

27<https://coral.ise.lehigh.edu/data-sets/qaplib>. 

28 

291. QAPLIB - A Quadratic Assignment Problem Library. The Websites 

30 <https://qaplib.mgi.polymtl.ca/> (updated 2018) and 

31 <https://coral.ise.lehigh.edu/data-sets/qaplib/> (updated 2011), including 

32 the benchmark instances, on visited 2023-10-21. 

332. Rainer E. Burkard, Stefan E. Karisch, and Franz Rendl. QAPLIB - A Quadratic 

34 Assignment Problem Library. Journal of Global Optimization. 10:391-403, 

35 1997. https://doi.org/10.1023/A:1008293323270. 

36""" 

37 

38 

39from typing import Final, Iterable, cast 

40 

41import numba # type: ignore 

42import numpy as np 

43from moptipy.api.component import Component 

44from moptipy.utils.logger import KeyValueLogSection 

45from moptipy.utils.nputils import ( 

46 DEFAULT_UNSIGNED_INT, 

47 int_range_to_dtype, 

48 is_np_int, 

49) 

50from moptipy.utils.strings import sanitize_name 

51from pycommons.types import check_int_range, check_to_int_range, type_error 

52 

53from moptipyapps.qap.qaplib import open_resource_stream 

54 

55 

56@numba.njit(cache=True, inline="always", fastmath=True, boundscheck=False) 

57def trivial_bounds(distances: np.ndarray, flows: np.ndarray) \ 

58 -> tuple[int, int]: 

59 """ 

60 Compute the trivial bounds for the QAP objective. 

61 

62 A trivial upper bound is to multiply the largest flow with the largest 

63 distance, the second-largest flow with the second-largest distance, the 

64 third-largest flow with the third-largest distance, and so on. 

65 

66 A trivial lower bound is to multiply the largest flow with the shortest 

67 distance, the second-largest flow with the second-shortest distance, the 

68 third-largest flow with the third-shortest distance, and so on. 

69 

70 :param distances: the distance matrix 

71 :param flows: the flow matrix 

72 :return: the lower and upper bounds 

73 

74 >>> dst = np.array([[0, 1, 2], [3, 0, 4], [5, 6, 0]]) 

75 >>> flws = np.array([[0, 95, 86], [23, 0, 55], [24, 43, 0]]) 

76 >>> 0*95 + 0*86 + 0*55 + 1*43 + 2*24 + 3*23 + 4*0 + 5*0 + 6*0 

77 160 

78 >>> 6*95 + 5*86 + 4*55 + 3*43 + 2*24 + 1*23 + 0*0 + 0*0 + 0*0 

79 1420 

80 >>> trivial_bounds(dst, flws) 

81 (160, 1420) 

82 """ 

83 n: int = len(distances) 

84 n *= n 

85 df_ub: Final[np.ndarray] = np.empty(n, DEFAULT_UNSIGNED_INT) 

86 df_ub[:] = distances.flatten() 

87 df_ub.sort() 

88 df_lb: Final[np.ndarray] = np.empty(n, DEFAULT_UNSIGNED_INT) 

89 df_lb[:] = df_ub[::-1] 

90 ff: Final[np.ndarray] = np.empty(n, DEFAULT_UNSIGNED_INT) 

91 ff[:] = flows.flatten() 

92 ff.sort() 

93 return (int(np.multiply(df_lb, ff, df_lb).sum()), 

94 int(np.multiply(df_ub, ff, df_ub).sum())) 

95 

96 

97def _flow_or_dist_to_int(val: str) -> int: 

98 """ 

99 Convert a flow or distance string to an integer. 

100 

101 :param val: the value 

102 :return: the integer 

103 """ 

104 return check_to_int_range(val, "value", 0, 1_000_000_000_000_000) 

105 

106 

107#: the instances of the QAPLib library 

108_INSTANCES: Final[tuple[str, ...]] = ( 

109 "bur26a", "bur26b", "bur26c", "bur26d", "bur26e", "bur26f", "bur26g", 

110 "bur26h", "chr12a", "chr12b", "chr12c", "chr15a", "chr15b", "chr15c", 

111 "chr18a", "chr18b", "chr20a", "chr20b", "chr20c", "chr22a", "chr22b", 

112 "chr25a", "els19", "esc16a", "esc16b", "esc16c", "esc16d", "esc16e", 

113 "esc16f", "esc16g", "esc16h", "esc16i", "esc16j", "esc32a", "esc32b", 

114 "esc32c", "esc32d", "esc32e", "esc32g", "esc32h", "esc64a", "esc128", 

115 "had12", "had14", "had16", "had18", "had20", "kra30a", "kra30b", "kra32", 

116 "lipa20a", "lipa20b", "lipa30a", "lipa30b", "lipa40a", "lipa40b", 

117 "lipa50a", "lipa50b", "lipa60a", "lipa60b", "lipa70a", "lipa70b", 

118 "lipa80a", "lipa80b", "lipa90a", "lipa90b", "nug12", "nug14", "nug15", 

119 "nug16a", "nug16b", "nug17", "nug18", "nug20", "nug21", "nug22", "nug24", 

120 "nug25", "nug27", "nug28", "nug30", "rou12", "rou15", "rou20", "scr12", 

121 "scr15", "scr20", "sko42", "sko49", "sko56", "sko64", "sko72", "sko81", 

122 "sko90", "sko100a", "sko100b", "sko100c", "sko100d", "sko100e", "sko100f", 

123 "ste36a", "ste36b", "ste36c", "tai12a", "tai12b", "tai15a", "tai15b", 

124 "tai17a", "tai20a", "tai20b", "tai25a", "tai25b", "tai30a", "tai30b", 

125 "tai35a", "tai35b", "tai40a", "tai40b", "tai50a", "tai50b", "tai60a", 

126 "tai60b", "tai64c", "tai80a", "tai80b", "tai100a", "tai100b", "tai150b", 

127 "tai256c", "tho30", "tho40", "tho150", "wil50", "wil100") 

128 

129#: the lower bounds provided at <https://qaplib.mgi.polymtl.ca/> 

130_BOUNDS: Final[dict[str, int]] = { 

131 "nug14": 1014, "nug15": 1150, "nug16a": 1610, "nug16b": 1240, 

132 "nug17": 1732, "nug18": 1930, "nug20": 2570, "nug21": 2438, "nug22": 3596, 

133 "nug24": 3488, "nug25": 3744, "nug27": 5234, "nug28": 5166, "nug30": 6124, 

134 "rou12": 235528, "rou15": 354210, "rou20": 725522, "scr12": 31410, 

135 "scr15": 51140, "scr20": 110030, "sko100a": 143846, "sko100b": 145522, 

136 "sko100c": 139881, "sko100d": 141289, "sko100e": 140893, "sko100f": 140691, 

137 "sko42": 15332, "sko49": 22650, "sko56": 33385, "sko64": 47017, 

138 "sko72": 64455, "sko81": 88359, "sko90": 112423, "ste36a": 9526, 

139 "ste36b": 15852, "ste36c": 8239110, "tai100a": 17853840, 

140 "tai100b": 1151591000, "tai12a": 224416, "tai12b": 39464925, 

141 "tai150b": 441786736, "tai15a": 388214, "tai15b": 51765268, 

142 "tai17a": 491812, "tai20a": 703482, "tai20b": 122455319, 

143 "tai256c": 44095032, "tai25a": 1167256, "tai25b": 344355646, 

144 "tai30a": 1706855, "tai30b": 637117113, "tai35a": 2216627, 

145 "tai35b": 269532400, "tai40a": 2843274, "tai40b": 608808400, 

146 "tai50a": 4390920, "tai50b": 431090700, "tai60a": 6325978, 

147 "tai60b": 592371800, "tai64c": 1855928, "tai80a": 11657010, 

148 "tai80b": 786298800, "tho150": 7620628, "tho30": 149936, "tho40": 226490, 

149 "wil100": 268955, "wil50": 48121, "bur26a": 5426670, "bur26b": 3817852, 

150 "bur26c": 5426795, "bur26d": 3821225, "bur26e": 5386879, "bur26f": 3782044, 

151 "bur26g": 10117172, "bur26h": 7098658, "chr12a": 9552, "chr12b": 9742, 

152 "chr12c": 11156, "chr15a": 9896, "chr15b": 7990, "chr15c": 9504, 

153 "chr18a": 11098, "chr18b": 1534, "chr20a": 2192, "chr20b": 2298, 

154 "chr20c": 14142, "chr22a": 6156, "chr22b": 6194, "chr25a": 3796, 

155 "els19": 17212548, "esc128": 64, "esc16a": 68, "esc16b": 292, 

156 "esc16c": 160, "esc16d": 16, "esc16e": 28, "esc16f": 0, "esc16g": 26, 

157 "esc16h": 996, "esc16i": 14, "esc16j": 8, "esc32a": 130, "esc32b": 168, 

158 "esc32c": 642, "esc32d": 200, "esc32e": 2, "esc32g": 6, "esc32h": 438, 

159 "esc64a": 116, "had12": 1652, "had14": 2724, "had16": 3720, "had18": 5358, 

160 "had20": 6922, "kra30a": 88900, "kra30b": 91420, "kra32": 88700, 

161 "lipa20a": 3683, "lipa20b": 27076, "lipa30a": 13178, "lipa30b": 151426, 

162 "lipa40a": 31538, "lipa40b": 476581, "lipa50a": 62093, "lipa50b": 1210244, 

163 "lipa60a": 107218, "lipa60b": 2520135, "lipa70a": 169755, 

164 "lipa70b": 4603200, "lipa80a": 253195, "lipa80b": 7763962, 

165 "lipa90a": 360630, "lipa90b": 12490441, "nug12": 578} 

166 

167#: The best-known solutions of the QAPLIB instances that are not yet solved to 

168#: optimality, as of 2024-05-09 on https://qaplib.mgi.polymtl.ca/ 

169_BKS: Final[dict[str, tuple[str, int]]] = { 

170 "sko90": ("T1991RTSFTQAP,T1995COISFTQAP", 115534), 

171 "sko100a": ("FF1993GHFTQAP", 152002), 

172 "sko100b": ("FF1993GHFTQAP", 153890), 

173 "sko100c": ("FF1993GHFTQAP", 147862), 

174 "sko100d": ("FF1993GHFTQAP", 149576), 

175 "sko100e": ("FF1993GHFTQAP", 149150), 

176 "sko100f": ("FF1993GHFTQAP", 149036), 

177 "tai30a": ("T1991RTSFTQAP,T1995COISFTQAP", 1818146), 

178 "tai35a": ("T1991RTSFTQAP,T1995COISFTQAP", 2422002), 

179 "tai35b": ("T1991RTSFTQAP,T1995COISFTQAP", 283315445), 

180 "tai40a": ("T1991RTSFTQAP,T1995COISFTQAP", 3139370), 

181 "tai40b": ("T1991RTSFTQAP,T1995COISFTQAP", 637250948), 

182 "tai50a": ("M2008AIOTITSAFTQAP", 4938796), 

183 "tai50b": ("T1991RTSFTQAP,T1995COISFTQAP", 458821517), 

184 "tai60a": ("M2005ATSAFTQAP", 7205962), 

185 "tai60b": ("T1991RTSFTQAP,T1995COISFTQAP", 608215054), 

186 "tai80a": ("M2008AIOTITSAFTQAP", 13499184), 

187 "tai80b": ("T1991RTSFTQAP,T1995COISFTQAP", 818415043), 

188 "tai100a": ("M2008AIOTITSAFTQAP", 21044752), 

189 "tai100b": ("T1991RTSFTQAP,T1995COISFTQAP", 1185996137), 

190 "tai150b": ("TG1997AMFTQAP", 498896643), 

191 "tai256c": ("S1997MMASFQAP", 44759294), 

192 "tho40": ("TB1994AISAAFTQAP", 240516), 

193 "tho150": ("M2003AMSAAFTQAP", 8133398), 

194 "wil100": ("FF1993GHFTQAP", 273038), 

195} 

196 

197 

198class Instance(Component): 

199 """An instance of the Quadratic Assignment Problem.""" 

200 

201 def __init__(self, distances: np.ndarray, flows: np.ndarray, 

202 lower_bound: int | None = None, 

203 upper_bound: int | None = None, 

204 name: str | None = None) -> None: 

205 """ 

206 Create an instance of the quadratic assignment problem. 

207 

208 :param distances: the distance matrix 

209 :param flows: the flow matrix 

210 :param lower_bound: the optional lower bound 

211 :param upper_bound: the optional upper bound 

212 :param name: the name of this instance 

213 """ 

214 super().__init__() 

215 if not isinstance(distances, np.ndarray): 

216 raise type_error(distances, "distances", np.ndarray) 

217 if not isinstance(flows, np.ndarray): 

218 raise type_error(flows, "flows", np.ndarray) 

219 shape: tuple[int, ...] = distances.shape 

220 if len(shape) != 2: 

221 raise ValueError("distance matrix must have two dimensions, but " 

222 f"has {len(shape)}, namely {shape}.") 

223 if shape[0] != shape[1]: 

224 raise ValueError( 

225 f"distance matrix must be square, but has shape {shape}.") 

226 if not is_np_int(distances.dtype): 

227 raise ValueError("distance matrix must be integer, but has " 

228 f"dtype {distances.dtype}.") 

229 if shape != flows.shape: 

230 raise ValueError( 

231 f"flow matrix has shape {flows.shape} and distance matrix has" 

232 f" shape {shape}, which are different.") 

233 if not is_np_int(flows.dtype): 

234 raise ValueError( 

235 f"flow matrix must be integer, but has dtype {flows.dtype}.") 

236 

237 lb, ub = trivial_bounds(distances, flows) 

238 if lower_bound is not None: 

239 lb = max(lb, check_int_range( 

240 lower_bound, "lower_bound", 0, 1_000_000_000_000_000)) 

241 if upper_bound is not None: 

242 ub = min(ub, check_int_range( 

243 upper_bound, "upper_bound", 0, 1_000_000_000_000_000)) 

244 if lb > ub: 

245 raise ValueError(f"lower bound = {lb} > upper_bound = {ub}!") 

246 dtype: Final[np.dtype] = int_range_to_dtype(min_value=0, max_value=ub) 

247 #: the scale of the problem 

248 self.n: Final[int] = shape[0] 

249 if name is None: 

250 name = f"qap{self.n}_{lb}_{ub}" 

251 else: 

252 sn: Final[str] = sanitize_name(name) 

253 if name != sn: 

254 raise ValueError(f"name={name!r} sanitizes to {sn!r}.") 

255 #: the name of this instance 

256 self.name: Final[str] = name 

257 #: the distance matrix 

258 self.distances: Final[np.ndarray] = \ 

259 distances.astype(dtype) if distances.dtype != dtype else distances 

260 #: the flows 

261 self.flows: Final[np.ndarray] = \ 

262 flows.astype(dtype) if flows.dtype != dtype else flows 

263 #: the lower bound for the QAP objective 

264 self.lower_bound: Final[int] = lb 

265 #: the upper bound for the QAP objective 

266 self.upper_bound: Final[int] = ub 

267 

268 def __str__(self): 

269 """ 

270 Get the name of this instance. 

271 

272 :return: :attr:`~name` 

273 """ 

274 return self.name 

275 

276 def bks(self) -> tuple[str, int]: 

277 """ 

278 Get the best-known solution, if known, the optimum, or lower bound. 

279 

280 A tuple with a string identifying the source of the value and a value 

281 corresponding to the best-known solution: 

282 

283 - `("OPT", xxx)`: the problem instance has been solved to optimality 

284 and `xxx` is the objective value of the optimum 

285 - `("LB", xxx)`: neither the optimum nor a best-known solution are 

286 available for this instance, so we return the lower bound 

287 

288 The data is based on https://qaplib.mgi.polymtl.ca/, visited on 

289 2024-05-09. The following sources are included: 

290 

291 - "FF1993GHFTQAP": Charles Fleurent and Jacques A. Ferland. Genetic 

292 Hybrids for the Quadratic Assignment Problem. In Panos M. Pardalos 

293 and Henry Wolkowicz, eds, *Quadratic Assignment and Related 

294 Problems, Proceedings of a DIMACS Workshop,* May 20-21, 1993. 

295 pages 173-187. Providence, RI, USA: American Mathematical Society. 

296 - "M2003AMSAAFTQAP": Alfonsas Misevičius, A Modified Simulated 

297 Annealing Algorithm for the Quadratic Assignment Problem. 

298 *Informatica* 14(4):497-514. January 2003. 

299 https://doi.org/10.15388/Informatica.2003.037. 

300 - "M2005ATSAFTQAP": Alfonsas Misevičius. A Tabu Search Algorithm for 

301 the Quadratic Assignment Problem. 

302 *Computational Optimization and Applications* 30(1):95-111. 2005. 

303 https://doi.org/10.1007/s10589-005-4562-x. 

304 - "M2008AIOTITSAFTQAP": Alfonsas Misevičius. An Implementation of 

305 the Iterated Tabu Search Algorithm for the Quadratic Assignment 

306 Problem. Working Paper. 2008. Kaunas, Lithuania: Kaunas University 

307 of Technology. 

308 - "S1997MMASFQAP": Thomas Stützle. MAX-MIN Ant System for Quadratic 

309 Assignment Problems. Research Report AIDA-97-04. 1997. Darmstadt, 

310 Germany: Department of Computer Schience, Darmstadt University of 

311 Technology. 

312 - "T1991RTSFTQAP": Éric Taillard. Robust Taboo Search for the 

313 Quadratic Assignment Problem. *Parallel Computing.* 

314 17(4-5):443-455. July 1991. 

315 - "T1995COISFTQAP": Éric D. Taillard. Comparison of Iterative 

316 Searches for the Quadratic Assignment Problem. *Location Science* 

317 3(2):87-105. 1995. 

318 - "TB1994AISAAFTQAP": Ulrich Wilhelm Thonemann and Andreas Bölte. 

319 An Improved Simulated Annealing Algorithm for the Quadratic 

320 Assignment Problem. Working Paper 1994. Paderborn, Germany: School 

321 of Business, Department of Production and Operations Research, 

322 University of Paderborn. 

323 - "TG1997AMFTQAP": Éric D. Taillard and Luca-Maria Gambardella. 

324 Adaptive Memories for the Quadratic Assignment Problem. 1997. 

325 Technical Report IDSIA-87-97. Lugano, Switzerland: IDSIA. 

326 

327 :return: a `tuple[str, int]` with the objective value of the 

328 best-known (if any is available), the optimum, or the lower bound. 

329 """ 

330 name: Final[str] = self.name 

331 if name in _BKS: 

332 return _BKS[name] 

333 if name in _BOUNDS: 

334 return "OPT", _BOUNDS[name] 

335 return "LB", self.lower_bound 

336 

337 def log_parameters_to(self, logger: KeyValueLogSection) -> None: 

338 """ 

339 Log all parameters of this instance as key-value pairs. 

340 

341 :param logger: the logger for the parameters 

342 """ 

343 super().log_parameters_to(logger) 

344 logger.key_value("n", self.n) 

345 logger.key_value("qapLowerBound", self.lower_bound) 

346 logger.key_value("qapUpperBound", self.upper_bound) 

347 

348 @staticmethod 

349 def from_qaplib_stream(stream: Iterable[str], 

350 lower_bound: int | None = None, 

351 upper_bound: int | None = None, 

352 name: str | None = None) -> "Instance": 

353 """ 

354 Load an instance from a QAPLib-formatted stream. 

355 

356 :param stream: the stream to load the data from 

357 :param lower_bound: the optional lower bound 

358 :param upper_bound: the optional upper bound 

359 :param name: the name of this instance 

360 :return: the instance 

361 

362 >>> ins = Instance.from_qaplib_stream([ 

363 ... "4", "", 

364 ... "1 2 3 4 5 6 7 8 9 10 11 12 13", 

365 ... " 14 15 16 ", "", 

366 ... "17 18 19 20 21 22 23 24 25 26 27", 

367 ... " 28 29 30 31 32"]) 

368 >>> ins.distances 

369 array([[17, 18, 19, 20], 

370 [21, 22, 23, 24], 

371 [25, 26, 27, 28], 

372 [29, 30, 31, 32]], dtype=int16) 

373 >>> ins.flows 

374 array([[ 1, 2, 3, 4], 

375 [ 5, 6, 7, 8], 

376 [ 9, 10, 11, 12], 

377 [13, 14, 15, 16]], dtype=int16) 

378 >>> ins.lower_bound 

379 2992 

380 >>> ins.upper_bound 

381 3672 

382 >>> ins.name 

383 'qap4_2992_3672' 

384 """ 

385 state: int = 0 

386 n: int | None = None 

387 n2: int = -1 

388 flows: list[int] = [] 

389 dists: list[int] = [] 

390 for oline in stream: 

391 line = oline.strip() 

392 if len(line) <= 0: 

393 continue 

394 if state == 0: 

395 n = check_to_int_range(line, "n", 1, 1_000_000) 

396 n2 = n * n 

397 state = 1 

398 else: 

399 row: Iterable[int] = map(_flow_or_dist_to_int, line.split()) 

400 if state == 1: 

401 flows.extend(row) 

402 if len(flows) >= n2: 

403 state = 2 

404 continue 

405 dists.extend(row) 

406 if len(dists) >= n2: 

407 state = 3 

408 break 

409 

410 if (n is None) or (n <= 0): 

411 raise ValueError(f"Invalid or unspecified size n={n}.") 

412 lll: int = len(flows) 

413 if lll != n2: 

414 raise ValueError( 

415 f"Invalid number of flows {lll}, should be n²={n}²={n2}.") 

416 lll = len(dists) 

417 if lll != n2: 

418 raise ValueError( 

419 f"Invalid number of distances {lll}, should be n²={n}²={n2}.") 

420 if state != 3: 

421 raise ValueError(f"Stream is incomplete, state={state}.") 

422 

423 return Instance(np.array(dists, DEFAULT_UNSIGNED_INT).reshape((n, n)), 

424 np.array(flows, DEFAULT_UNSIGNED_INT).reshape((n, n)), 

425 lower_bound, upper_bound, name) 

426 

427 @staticmethod 

428 def list_resources() -> tuple[str, ...]: 

429 """ 

430 Get a tuple of all the QAP-lib instances available as resource. 

431 

432 The original data can be found at <https://qaplib.mgi.polymtl.ca> and 

433 <https://coral.ise.lehigh.edu/data-sets/qaplib>. 

434 

435 :return: the tuple with the instance names 

436 

437 >>> len(Instance.list_resources()) 

438 134 

439 """ 

440 return _INSTANCES 

441 

442 @staticmethod 

443 def from_resource(name: str) -> "Instance": 

444 """ 

445 Load a QAP-Lib instance from a resource. 

446 

447 The original data can be found at <https://qaplib.mgi.polymtl.ca> and 

448 <https://coral.ise.lehigh.edu/data-sets/qaplib>. 

449 

450 :param name: the name string 

451 :return: the instance 

452 

453 >>> insta = Instance.from_resource("chr25a") 

454 >>> print(insta.n) 

455 25 

456 >>> print(insta.name) 

457 chr25a 

458 >>> print(insta.lower_bound) 

459 3796 

460 >>> print(insta.upper_bound) 

461 50474 

462 """ 

463 if not isinstance(name, str): 

464 raise type_error(name, "name", str) 

465 container: Final = Instance.from_resource 

466 inst_attr: Final[str] = f"__inst_{name}" 

467 if hasattr(container, inst_attr): # instance loaded? 

468 return cast("Instance", getattr(container, inst_attr)) 

469 

470 lb: int | None = _BOUNDS.get(name, None) 

471 with open_resource_stream(f"{name}.dat") as stream: 

472 inst: Final[Instance] = Instance.from_qaplib_stream( 

473 stream, name=name, lower_bound=lb) 

474 

475 if inst.n <= 1000: 

476 setattr(container, inst_attr, inst) 

477 return inst