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

131 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-05-28 09:42 +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", "tai10a", "tai10b", "tai12a", "tai12b", 

124 "tai15a", "tai15b", "tai17a", "tai20a", "tai20b", "tai25a", "tai25b", 

125 "tai30a", "tai30b", "tai35a", "tai35b", "tai40a", "tai40b", "tai50a", 

126 "tai50b", "tai60a", "tai60b", "tai64c", "tai80a", "tai80b", "tai100a", 

127 "tai100b", "tai150b", "tai256c", "tho30", "tho40", "tho150", "wil50", 

128 "wil100") 

129 

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

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

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

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

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

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

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

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

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

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

140 "ste36b": 15852, "ste36c": 8239110, "tai10a": 135028, "tai10b": 1183760, 

141 "tai12a": 224416, "tai12b": 39464925, "tai150b": 441786736, 

142 "tai15a": 388214, "tai15b": 51765268, "tai17a": 491812, "tai20a": 703482, 

143 "tai20b": 122455319, "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, "tai100a": 17853840, "tai100b": 1151591000, 

149 "tai256c": 44095032, "tho150": 7620628, "tho30": 149936, "tho40": 226490, 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

167 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

196} 

197 

198 

199class Instance(Component): 

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

201 

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

203 lower_bound: int | None = None, 

204 upper_bound: int | None = None, 

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

206 """ 

207 Create an instance of the quadratic assignment problem. 

208 

209 :param distances: the distance matrix 

210 :param flows: the flow matrix 

211 :param lower_bound: the optional lower bound 

212 :param upper_bound: the optional upper bound 

213 :param name: the name of this instance 

214 """ 

215 super().__init__() 

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

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

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

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

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

221 if len(shape) != 2: 

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

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

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

225 raise ValueError( 

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

227 if not is_np_int(distances.dtype): 

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

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

230 if shape != flows.shape: 

231 raise ValueError( 

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

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

234 if not is_np_int(flows.dtype): 

235 raise ValueError( 

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

237 

238 lb, ub = trivial_bounds(distances, flows) 

239 if lower_bound is not None: 

240 lb = max(lb, check_int_range( 

241 lower_bound, "lower_bound", 0, 1_000_000_000_000_000)) 

242 if upper_bound is not None: 

243 ub = min(ub, check_int_range( 

244 upper_bound, "upper_bound", 0, 1_000_000_000_000_000)) 

245 if lb > ub: 

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

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

248 #: the scale of the problem 

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

250 if name is None: 

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

252 else: 

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

254 if name != sn: 

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

256 #: the name of this instance 

257 self.name: Final[str] = name 

258 #: the distance matrix 

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

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

261 #: the flows 

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

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

264 #: the lower bound for the QAP objective 

265 self.lower_bound: Final[int] = lb 

266 #: the upper bound for the QAP objective 

267 self.upper_bound: Final[int] = ub 

268 

269 def __str__(self): 

270 """ 

271 Get the name of this instance. 

272 

273 :return: :attr:`~name` 

274 """ 

275 return self.name 

276 

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

278 """ 

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

280 

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

282 corresponding to the best-known solution: 

283 

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

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

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

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

288 

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

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

291 

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

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

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

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

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

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

298 Annealing Algorithm for the Quadratic Assignment Problem. 

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

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

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

302 the Quadratic Assignment Problem. 

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

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

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

306 the Iterated Tabu Search Algorithm for the Quadratic Assignment 

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

308 of Technology. 

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

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

311 Germany: Department of Computer Schience, Darmstadt University of 

312 Technology. 

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

314 Quadratic Assignment Problem. *Parallel Computing.* 

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

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

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

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

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

320 An Improved Simulated Annealing Algorithm for the Quadratic 

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

322 of Business, Department of Production and Operations Research, 

323 University of Paderborn. 

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

325 Adaptive Memories for the Quadratic Assignment Problem. 1997. 

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

327 

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

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

330 """ 

331 name: Final[str] = self.name 

332 if name in _BKS: 

333 return _BKS[name] 

334 if name in _BOUNDS: 

335 return "OPT", _BOUNDS[name] 

336 return "LB", self.lower_bound 

337 

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

339 """ 

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

341 

342 :param logger: the logger for the parameters 

343 """ 

344 super().log_parameters_to(logger) 

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

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

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

348 

349 @staticmethod 

350 def from_qaplib_stream(stream: Iterable[str], 

351 lower_bound: int | None = None, 

352 upper_bound: int | None = None, 

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

354 """ 

355 Load an instance from a QAPLib-formatted stream. 

356 

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

358 :param lower_bound: the optional lower bound 

359 :param upper_bound: the optional upper bound 

360 :param name: the name of this instance 

361 :return: the instance 

362 

363 >>> ins = Instance.from_qaplib_stream([ 

364 ... "4", "", 

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

366 ... " 14 15 16 ", "", 

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

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

369 >>> ins.distances 

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

371 [21, 22, 23, 24], 

372 [25, 26, 27, 28], 

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

374 >>> ins.flows 

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

376 [ 5, 6, 7, 8], 

377 [ 9, 10, 11, 12], 

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

379 >>> ins.lower_bound 

380 2992 

381 >>> ins.upper_bound 

382 3672 

383 >>> ins.name 

384 'qap4_2992_3672' 

385 """ 

386 state: int = 0 

387 n: int | None = None 

388 n2: int = -1 

389 flows: list[int] = [] 

390 dists: list[int] = [] 

391 for oline in stream: 

392 line = oline.strip() 

393 if len(line) <= 0: 

394 continue 

395 if state == 0: 

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

397 n2 = n * n 

398 state = 1 

399 else: 

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

401 if state == 1: 

402 flows.extend(row) 

403 if len(flows) >= n2: 

404 state = 2 

405 continue 

406 dists.extend(row) 

407 if len(dists) >= n2: 

408 state = 3 

409 break 

410 

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

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

413 lll: int = len(flows) 

414 if lll != n2: 

415 raise ValueError( 

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

417 lll = len(dists) 

418 if lll != n2: 

419 raise ValueError( 

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

421 if state != 3: 

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

423 

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

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

426 lower_bound, upper_bound, name) 

427 

428 @staticmethod 

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

430 """ 

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

432 

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

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

435 

436 :return: the tuple with the instance names 

437 

438 >>> len(Instance.list_resources()) 

439 136 

440 """ 

441 return _INSTANCES 

442 

443 @staticmethod 

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

445 """ 

446 Load a QAP-Lib instance from a resource. 

447 

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

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

450 

451 :param name: the name string 

452 :return: the instance 

453 

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

455 >>> print(insta.n) 

456 25 

457 >>> print(insta.name) 

458 chr25a 

459 >>> print(insta.lower_bound) 

460 3796 

461 >>> print(insta.upper_bound) 

462 50474 

463 """ 

464 if not isinstance(name, str): 

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

466 container: Final = Instance.from_resource 

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

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

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

470 

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

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

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

474 stream, name=name, lower_bound=lb) 

475 

476 if inst.n <= 1000: 

477 setattr(container, inst_attr, inst) 

478 return inst