Coverage for picos/modeling/file_in.py: 3.88%

361 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-04-12 07:53 +0000

1# ------------------------------------------------------------------------------ 

2# Copyright (C) 2012-2017 Guillaume Sagnol 

3# Copyright (C) 2019 Maximilian Stahlberg 

4# 

5# This file is part of PICOS. 

6# 

7# PICOS is free software: you can redistribute it and/or modify it under the 

8# terms of the GNU General Public License as published by the Free Software 

9# Foundation, either version 3 of the License, or (at your option) any later 

10# version. 

11# 

12# PICOS is distributed in the hope that it will be useful, but WITHOUT ANY 

13# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 

14# A PARTICULAR PURPOSE. See the GNU General Public License for more details. 

15# 

16# You should have received a copy of the GNU General Public License along with 

17# this program. If not, see <http://www.gnu.org/licenses/>. 

18# ------------------------------------------------------------------------------ 

19 

20"""Functions for reading optimization problems from a file.""" 

21 

22# ------------------------------------------------------------------------------ 

23# TODO: This file is using ad-hoc parser implementations. Use a proper parser 

24# generation library and a unified 'read' function for different formats. 

25# ------------------------------------------------------------------------------ 

26 

27import warnings 

28 

29import cvxopt 

30import numpy 

31 

32from ..apidoc import api_end, api_start 

33from ..expressions import (AffineExpression, Constant, IntegerVariable, 

34 RealVariable, SymmetricVariable) 

35from .problem import Problem 

36 

37_API_START = api_start(globals()) 

38# ------------------------------- 

39 

40 

41def _spmatrix(*args, **kwargs): 

42 """Create a CVXOPT sparse matrix. 

43 

44 A wrapper around :func:`cvxopt.spmatrix` that converts indices to 

45 :class:`int`, if necessary. 

46 

47 Works around PICOS sometimes passing indices as :class:`numpy:numpy.int64`. 

48 """ 

49 try: 

50 return cvxopt.spmatrix(*args, **kwargs) 

51 except TypeError as error: 

52 # CVXOPT does not like NumPy's int64 scalar type for indices, so attempt 

53 # to convert all indices to Python's int. 

54 if str(error) == "non-numeric type in list": 

55 newargs = list(args) 

56 

57 for argNum, arg in enumerate(args): 

58 if argNum in (1, 2): # Positional I, J. 

59 newargs[argNum] = [int(x) for x in args[argNum]] 

60 

61 for kw in "IJ": 

62 if kw in kwargs: 

63 kwargs[kw] = [int(x) for x in kwargs[kw]] 

64 

65 return cvxopt.spmatrix(*newargs, **kwargs) 

66 else: 

67 raise 

68 

69 

70def _break_cols(mat, sizes): 

71 """Help with the reading of CBF files.""" 

72 n = len(sizes) 

73 I, J, V = [], [], [] 

74 for i in range(n): 

75 I.append([]) 

76 J.append([]) 

77 V.append([]) 

78 cumsz = numpy.cumsum(sizes) 

79 import bisect 

80 

81 for i, j, v in zip(mat.I, mat.J, mat.V): 

82 block = bisect.bisect(cumsz, j) 

83 I[block].append(i) 

84 V[block].append(v) 

85 if block == 0: 

86 J[block].append(j) 

87 else: 

88 J[block].append(j - cumsz[block - 1]) 

89 return [ 

90 _spmatrix(V[k], I[k], J[k], (mat.size[0], sz)) 

91 for k, sz in enumerate(sizes) 

92 ] 

93 

94 

95def _break_rows(mat, sizes): 

96 """Help with the reading of CBF files.""" 

97 n = len(sizes) 

98 I, J, V = [], [], [] 

99 for i in range(n): 

100 I.append([]) 

101 J.append([]) 

102 V.append([]) 

103 cumsz = numpy.cumsum(sizes) 

104 import bisect 

105 

106 for i, j, v in zip(mat.I, mat.J, mat.V): 

107 block = bisect.bisect(cumsz, i) 

108 J[block].append(j) 

109 V[block].append(v) 

110 if block == 0: 

111 I[block].append(i) 

112 else: 

113 I[block].append(i - cumsz[block - 1]) 

114 return [ 

115 _spmatrix(V[k], I[k], J[k], (sz, mat.size[1])) 

116 for k, sz in enumerate(sizes) 

117 ] 

118 

119 

120def _block_idx(i, sizes): 

121 """Help with the reading of CBF files.""" 

122 # if there are blocks of sizes n1,...,nk and i is 

123 # the index of an element of the big vectorized variable, 

124 # returns the block of i and its index inside the sub-block. 

125 cumsz = numpy.cumsum(sizes) 

126 import bisect 

127 

128 block = bisect.bisect(cumsz, i) 

129 return block, (i if block == 0 else i - cumsz[block - 1]) 

130 

131 

132def _read_cbf_block(P, blocname, f, parsed_blocks): 

133 """Help with the reading of CBF files.""" 

134 if blocname == "OBJSENSE": 

135 objsense = f.readline().split()[0].lower() 

136 P.set_objective(objsense, P.objective.normalized.function) 

137 return None 

138 elif blocname == "PSDVAR": 

139 n = int(f.readline()) 

140 vardims = [] 

141 XX = [] 

142 for i in range(n): 

143 ni = int(f.readline()) 

144 vardims.append(ni) 

145 Xi = SymmetricVariable("X[" + str(i) + "]", (ni, ni)) 

146 XX.append(Xi) 

147 P.add_constraint(Xi >> 0) 

148 return vardims, XX 

149 elif blocname == "VAR": 

150 Nscalar, ncones = [int(fi) for fi in f.readline().split()] 

151 tot_dim = 0 

152 var_structure = [] 

153 xx = [] 

154 for i in range(ncones): 

155 lsplit = f.readline().split() 

156 tp, dim = lsplit[0], int(lsplit[1]) 

157 tot_dim += dim 

158 var_structure.append(dim) 

159 if tp == "F": 

160 xi = RealVariable("x[" + str(i) + "]", dim) 

161 elif tp == "L+": 

162 xi = RealVariable("x[" + str(i) + "]", dim, lower=0) 

163 elif tp == "L-": 

164 xi = RealVariable("x[" + str(i) + "]", dim, upper=0) 

165 elif tp == "L=": 

166 xi = RealVariable("x[" + str(i) + "]", dim, lower=0, upper=0) 

167 elif tp == "Q": 

168 xi = RealVariable("x[" + str(i) + "]", dim) 

169 P.add_constraint(abs(xi[1:]) < xi[0]) 

170 elif tp == "QR": 

171 xi = RealVariable("x[" + str(i) + "]", dim) 

172 P.add_constraint(abs(xi[2:]) ** 2 < 2 * xi[0] * xi[1]) 

173 xx.append(xi) 

174 if tot_dim != Nscalar: 

175 raise Exception("VAR dimensions do not match the header") 

176 return Nscalar, var_structure, xx 

177 elif blocname == "INT": 

178 n = int(f.readline()) 

179 ints = {} 

180 for k in range(n): 

181 j = int(f.readline()) 

182 i, col = _block_idx(j, parsed_blocks["VAR"][1]) 

183 ints.setdefault(i, []) 

184 ints[i].append(col) 

185 x = parsed_blocks["VAR"][2] 

186 for i in ints: 

187 if len(ints[i]) == x[i].size[0]: 

188 x[i] = IntegerVariable( 

189 x[i].name, x[i].shape, lower=x[i]._lower, upper=x[i]._upper) 

190 else: 

191 x.append( 

192 IntegerVariable("x_int[" + str(i) + "]", len(ints[i])) 

193 ) 

194 for k, j in enumerate(ints[i]): 

195 P.add_constraint(x[i][j] == x[-1][k]) 

196 return x 

197 elif blocname == "CON": 

198 Ncons, ncones = [int(fi) for fi in f.readline().split()] 

199 cons_structure = [] 

200 tot_dim = 0 

201 for i in range(ncones): 

202 lsplit = f.readline().split() 

203 tp, dim = lsplit[0], int(lsplit[1]) 

204 tot_dim += dim 

205 cons_structure.append((tp, dim)) 

206 if tot_dim != Ncons: 

207 raise Exception("CON dimensions do not match the header") 

208 return Ncons, cons_structure 

209 elif blocname == "PSDCON": 

210 n = int(f.readline()) 

211 psdcons_structure = [] 

212 for i in range(n): 

213 ni = int(f.readline()) 

214 psdcons_structure.append(ni) 

215 return psdcons_structure 

216 elif blocname == "OBJACOORD": 

217 n = int(f.readline()) 

218 J = [] 

219 V = [] 

220 for i in range(n): 

221 lsplit = f.readline().split() 

222 j, v = int(lsplit[0]), float(lsplit[1]) 

223 J.append(j) 

224 V.append(v) 

225 return _spmatrix(V, [0] * len(J), J, (1, parsed_blocks["VAR"][0])) 

226 elif blocname == "OBJBCOORD": 

227 return float(f.readline()) 

228 elif blocname == "OBJFCOORD": 

229 n = int(f.readline()) 

230 Fobj = [ 

231 _spmatrix([], [], [], (ni, ni)) for ni in parsed_blocks["PSDVAR"][0] 

232 ] 

233 for k in range(n): 

234 lsplit = f.readline().split() 

235 j, row, col, v = ( 

236 int(lsplit[0]), 

237 int(lsplit[1]), 

238 int(lsplit[2]), 

239 float(lsplit[3]), 

240 ) 

241 Fobj[j][row, col] = v 

242 if row != col: 

243 Fobj[j][col, row] = v 

244 return Fobj 

245 elif blocname == "FCOORD": 

246 n = int(f.readline()) 

247 Fblocks = {} 

248 for k in range(n): 

249 lsplit = f.readline().split() 

250 i, j, row, col, v = ( 

251 int(lsplit[0]), 

252 int(lsplit[1]), 

253 int(lsplit[2]), 

254 int(lsplit[3]), 

255 float(lsplit[4]), 

256 ) 

257 if i not in Fblocks: 

258 Fblocks[i] = [ 

259 _spmatrix([], [], [], (ni, ni)) 

260 for ni in parsed_blocks["PSDVAR"][0] 

261 ] 

262 Fblocks[i][j][row, col] = v 

263 if row != col: 

264 Fblocks[i][j][col, row] = v 

265 return Fblocks 

266 elif blocname == "ACOORD": 

267 n = int(f.readline()) 

268 J = [] 

269 V = [] 

270 I = [] 

271 for k in range(n): 

272 lsplit = f.readline().split() 

273 i, j, v = int(lsplit[0]), int(lsplit[1]), float(lsplit[2]) 

274 I.append(i) 

275 J.append(j) 

276 V.append(v) 

277 return _spmatrix( 

278 V, I, J, (parsed_blocks["CON"][0], parsed_blocks["VAR"][0]) 

279 ) 

280 elif blocname == "BCOORD": 

281 n = int(f.readline()) 

282 V = [] 

283 I = [] 

284 for k in range(n): 

285 lsplit = f.readline().split() 

286 i, v = int(lsplit[0]), float(lsplit[1]) 

287 I.append(i) 

288 V.append(v) 

289 return _spmatrix(V, I, [0] * len(I), (parsed_blocks["CON"][0], 1)) 

290 elif blocname == "HCOORD": 

291 n = int(f.readline()) 

292 Hblocks = {} 

293 for k in range(n): 

294 lsplit = f.readline().split() 

295 i, j, row, col, v = ( 

296 int(lsplit[0]), 

297 int(lsplit[1]), 

298 int(lsplit[2]), 

299 int(lsplit[3]), 

300 float(lsplit[4]), 

301 ) 

302 if j not in Hblocks: 

303 Hblocks[j] = [ 

304 _spmatrix([], [], [], (ni, ni)) 

305 for ni in parsed_blocks["PSDCON"] 

306 ] 

307 Hblocks[j][i][row, col] = v 

308 if row != col: 

309 Hblocks[j][i][col, row] = v 

310 return Hblocks 

311 elif blocname == "DCOORD": 

312 n = int(f.readline()) 

313 Dblocks = [ 

314 _spmatrix([], [], [], (ni, ni)) for ni in parsed_blocks["PSDCON"] 

315 ] 

316 for k in range(n): 

317 lsplit = f.readline().split() 

318 i, row, col, v = ( 

319 int(lsplit[0]), 

320 int(lsplit[1]), 

321 int(lsplit[2]), 

322 float(lsplit[3]), 

323 ) 

324 Dblocks[i][row, col] = v 

325 if row != col: 

326 Dblocks[i][col, row] = v 

327 return Dblocks 

328 else: 

329 raise Exception("unexpected block name") 

330 

331 

332def import_cbf(filename): 

333 """Create a :class:`~picos.Problem` from a CBF file. 

334 

335 The created problem contains one (multidimensional) variable for each cone 

336 specified in the section ``VAR`` of the .cbf file, and one 

337 (multidimmensional) constraint for each cone specified in the sections 

338 ``CON`` and ``PSDCON``. 

339 

340 :returns: A tuple ``(P, x, X, params)`` where 

341 

342 - ``P`` is the imported picos :class:`~picos.Problem` object, 

343 - ``x`` is a list of multidimensional variables representing the scalar 

344 variables found in the file, 

345 - ``X`` is a list of symmetric variables representing the positive 

346 semidefinite variables found in the file, and 

347 - ``params`` is a dictionary containing PICOS cosntants used to define 

348 the problem. Indexing is with respect to the blocks of variables as 

349 defined in the sections ``VAR`` and ``CON`` of the file. 

350 """ 

351 P = Problem() 

352 

353 try: 

354 f = open(filename, "r") 

355 except IOError: 

356 filename += ".cbf" 

357 f = open(filename, "r") 

358 

359 line = f.readline() 

360 while not line.startswith("VER"): 

361 line = f.readline() 

362 

363 ver = int(f.readline()) 

364 if ver != 1: 

365 warnings.warn("CBF file has a version other than 1.") 

366 

367 structure_keywords = ["OBJSENSE", "PSDVAR", "VAR", "INT", "PSDCON", "CON"] 

368 data_keywords = [ 

369 "OBJFCOORD", 

370 "OBJACOORD", 

371 "OBJBCOORD", 

372 "FCOORD", 

373 "ACOORD", 

374 "BCOORD", 

375 "HCOORD", 

376 "DCOORD", 

377 ] 

378 

379 structure_mode = True # still parsing structure blocks 

380 seen_blocks = [] 

381 parsed_blocks = {} 

382 while True: 

383 line = f.readline() 

384 if not line: 

385 break 

386 lsplit = line.split() 

387 if lsplit and lsplit[0] in structure_keywords: 

388 if lsplit[0] == "INT" and ("VAR" not in seen_blocks): 

389 raise Exception("INT BLOCK before VAR BLOCK") 

390 if lsplit[0] == "CON" and not ( 

391 "VAR" in seen_blocks or "PSDVAR" in seen_blocks 

392 ): 

393 raise Exception("CON BLOCK before VAR/PSDVAR BLOCK") 

394 if lsplit[0] == "PSDCON" and not ( 

395 "VAR" in seen_blocks or "PSDVAR" in seen_blocks 

396 ): 

397 raise Exception("PSDCON BLOCK before VAR/PSDVAR BLOCK") 

398 if lsplit[0] == "VAR" and ( 

399 "CON" in seen_blocks or "PSDCON" in seen_blocks 

400 ): 

401 raise Exception("VAR BLOCK after CON/PSDCON BLOCK") 

402 if lsplit[0] == "PSDVAR" and ( 

403 "CON" in seen_blocks or "PSDCON" in seen_blocks 

404 ): 

405 raise Exception("PSDVAR BLOCK after CON/PSDCON BLOCK") 

406 if structure_mode: 

407 parsed_blocks[lsplit[0]] = _read_cbf_block( 

408 P, lsplit[0], f, parsed_blocks 

409 ) 

410 seen_blocks.append(lsplit[0]) 

411 else: 

412 raise Exception("Structure keyword after first data item") 

413 if lsplit and lsplit[0] in data_keywords: 

414 if "OBJSENSE" not in seen_blocks: 

415 raise Exception("missing OBJSENSE block") 

416 if not ("VAR" in seen_blocks or "PSDVAR" in seen_blocks): 

417 raise Exception("missing VAR/PSDVAR block") 

418 if lsplit[0] in ("OBJFCOORD", "FCOORD") and ( 

419 "PSDVAR" not in seen_blocks 

420 ): 

421 raise Exception("missing PSDVAR block") 

422 if lsplit[0] in ("OBJACOORD", "ACOORD", "HCOORD") and ( 

423 "VAR" not in seen_blocks 

424 ): 

425 raise Exception("missing VAR block") 

426 if lsplit[0] in ("DCOORD", "HCOORD") and ( 

427 "PSDCON" not in seen_blocks 

428 ): 

429 raise Exception("missing PSDCON block") 

430 structure_mode = False 

431 parsed_blocks[lsplit[0]] = _read_cbf_block( 

432 P, lsplit[0], f, parsed_blocks 

433 ) 

434 seen_blocks.append(lsplit[0]) 

435 

436 f.close() 

437 # variables 

438 if "VAR" in parsed_blocks: 

439 Nvars, varsz, x = parsed_blocks["VAR"] 

440 else: 

441 x = None 

442 

443 if "INT" in parsed_blocks: 

444 x = parsed_blocks["INT"] 

445 

446 if "PSDVAR" in parsed_blocks: 

447 psdsz, X = parsed_blocks["PSDVAR"] 

448 else: 

449 X = None 

450 

451 # objective 

452 obj_constant = parsed_blocks.get("OBJBCOORD", 0) 

453 bobj = Constant("bobj", obj_constant) 

454 obj = Constant("bobj", obj_constant) 

455 

456 aobj = {} 

457 if "OBJACOORD" in parsed_blocks: 

458 obj_vecs = _break_cols(parsed_blocks["OBJACOORD"], varsz) 

459 aobj = {} 

460 for k, v in enumerate(obj_vecs): 

461 if v: 

462 aobj[k] = Constant("c[" + str(k) + "]", v) 

463 obj += aobj[k] * x[k] 

464 

465 Fobj = {} 

466 if "OBJFCOORD" in parsed_blocks: 

467 Fbl = parsed_blocks["OBJFCOORD"] 

468 for i, Fi in enumerate(Fbl): 

469 if Fi: 

470 Fobj[i] = Constant("F[" + str(i) + "]", Fi) 

471 obj += Fobj[i] | X[i] 

472 

473 P.set_objective(P.objective.normalized.direction, obj) 

474 

475 # cone constraints 

476 bb = {} 

477 AA = {} 

478 FF = {} 

479 if "CON" in parsed_blocks: 

480 Ncons, structcons = parsed_blocks["CON"] 

481 szcons = [s for tp, s in structcons] 

482 

483 b = parsed_blocks.get("BCOORD", _spmatrix([], [], [], (Ncons, 1))) 

484 bvecs = _break_rows(b, szcons) 

485 consexp = [] 

486 for i, bi in enumerate(bvecs): 

487 bb[i] = Constant("b[" + str(i) + "]", bi) 

488 consexp.append(Constant("b[" + str(i) + "]", bi)) 

489 

490 A = parsed_blocks.get("ACOORD", _spmatrix([], [], [], (Ncons, Nvars))) 

491 Ablc = _break_rows(A, szcons) 

492 for i, Ai in enumerate(Ablc): 

493 Aiblocs = _break_cols(Ai, varsz) 

494 for j, Aij in enumerate(Aiblocs): 

495 if Aij: 

496 AA[i, j] = Constant("A[" + str((i, j)) + "]", Aij) 

497 consexp[i] += AA[i, j] * x[j] 

498 

499 Fcoords = parsed_blocks.get("FCOORD", {}) 

500 for k, mats in Fcoords.items(): 

501 i, row = _block_idx(k, szcons) 

502 row_exp = AffineExpression.zero() 

503 for j, mat in enumerate(mats): 

504 if mat: 

505 FF[i, j, row] = Constant("F[" + str((i, j, row)) + "]", mat) 

506 row_exp += FF[i, j, row] | X[j] 

507 

508 consexp[i] += ( 

509 "e_" + str(row) + "(" + str(szcons[i]) + ",1)" 

510 ) * row_exp 

511 

512 for i, (tp, sz) in enumerate(structcons): 

513 if tp == "F": 

514 continue 

515 elif tp == "L-": 

516 P.add_constraint(consexp[i] < 0) 

517 elif tp == "L+": 

518 P.add_constraint(consexp[i] > 0) 

519 elif tp == "L=": 

520 P.add_constraint(consexp[i] == 0) 

521 elif tp == "Q": 

522 P.add_constraint(abs(consexp[i][1:]) < consexp[i][0]) 

523 elif tp == "QR": 

524 P.add_constraint( 

525 abs(consexp[i][2:]) ** 2 < 2 * consexp[i][0] * consexp[i][1] 

526 ) 

527 else: 

528 raise Exception("unexpected cone type") 

529 

530 DD = {} 

531 HH = {} 

532 if "PSDCON" in parsed_blocks: 

533 Dblocks = parsed_blocks.get( 

534 "DCOORD", 

535 [_spmatrix([], [], [], (ni, ni)) for ni in parsed_blocks["PSDCON"]], 

536 ) 

537 Hblocks = parsed_blocks.get("HCOORD", {}) 

538 

539 consexp = [] 

540 for i, Di in enumerate(Dblocks): 

541 DD[i] = Constant("D[" + str(i) + "]", Di) 

542 consexp.append(Constant("D[" + str(i) + "]", Di)) 

543 

544 for j, Hj in Hblocks.items(): 

545 i, col = _block_idx(j, varsz) 

546 for k, Hij in enumerate(Hj): 

547 if Hij: 

548 HH[k, i, col] = Constant("H[" + str((k, i, col)) + "]", Hij) 

549 consexp[k] += HH[k, i, col] * x[i][col] 

550 

551 for exp in consexp: 

552 P.add_constraint(exp >> 0) 

553 

554 params = { 

555 "aobj": aobj, 

556 "bobj": bobj, 

557 "Fobj": Fobj, 

558 "A": AA, 

559 "b": bb, 

560 "F": FF, 

561 "D": DD, 

562 "H": HH, 

563 } 

564 

565 return P, x, X, params 

566 

567 

568# -------------------------------------- 

569__all__ = api_end(_API_START, globals())