Coverage for picos/modeling/options.py: 83.07%

189 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-03-26 07:46 +0000

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

2# Copyright (C) 2019-2020 Maximilian Stahlberg 

3# 

4# This file is part of PICOS. 

5# 

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

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

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

9# version. 

10# 

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

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

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

14# 

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

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

17# ------------------------------------------------------------------------------ 

18 

19"""Optimization solver parameter handling.""" 

20 

21# ------------------------------------------------------------------------------ 

22# NOTE: When modifying tolerance options, be sure to also modify tolerances.rst. 

23# ------------------------------------------------------------------------------ 

24 

25import fnmatch 

26import types 

27 

28from ..apidoc import api_end, api_start 

29from ..solvers import all_solvers, Solver 

30 

31_API_START = api_start(globals()) 

32# ------------------------------- 

33 

34 

35OPTIONS = [ 

36 # General options. 

37 # -------------------------------------------------------------------------- 

38 

39 ("strict_options", bool, False, """ 

40 Whether unsupported general options will raise an 

41 :class:`~.solver.UnsupportedOptionError` exception, instead of printing 

42 a warning."""), 

43 

44 ("verbosity", int, 0, """ 

45 Verbosity level. 

46 

47 - ``-1`` attempts to suppress all output, even errros. 

48 - ``0`` only generates warnings and errors. 

49 - ``1`` generates standard informative output. 

50 - ``2`` or larger prints additional information for debugging purposes. 

51 """, lambda n: -1 <= n), 

52 

53 ("license_warnings", bool, True, """ 

54 Whether solvers are allowed to ignore the :ref:`verbosity 

55 <option_verbosity>` option to print licensing related warnings. 

56 

57 See also the global setting :data:`~.settings.LICENSE_WARNINGS`. 

58 """), 

59 

60 ("solver", str, None, """ 

61 The solver to use. 

62 

63 See also the global settings :data:`~.settings.SOLVER_BLACKLIST`, 

64 :data:`~.settings.SOLVER_WHITELIST` and 

65 :data:`~.settings.NONFREE_SOLVERS`. 

66 

67 - :obj:`None` to let PICOS choose. 

68 - """ + """ 

69 - """.join('``"{0}"`` for :class:`{1} <picos.solvers.{1}>`.' 

70 .format(name, solver.__name__) 

71 for name, solver in all_solvers().items()) + """ 

72 

73 This option is ignored when :ref:`ad_hoc_solver <option_ad_hoc_solver>` 

74 is set. 

75 

76 .. note:: 

77 

78 :func:`picos.available_solvers() <picos.available_solvers>` returns 

79 a list of names of solvers that are available at runtime. 

80 """, lambda value: value is None or value in all_solvers().keys()), 

81 

82 ("ad_hoc_solver", type, None, """ 

83 The solver to use as a :class:`~.solvers.solver.Solver` subclass. 

84 

85 This allows solver implementations to be shipped independent of PICOS. 

86 

87 If set, takes precedence over :ref:`solver <option_solver>`.""", 

88 lambda value: value is None or issubclass(value, Solver)), 

89 

90 ("primals", bool, True, """ 

91 Whether to request a primal solution. 

92 

93 - :obj:`True` will raise an exception if no optimal primal solution is 

94 found. 

95 - :obj:`None` will accept and apply also incomplete, infeasible or 

96 suboptimal primal solutions. 

97 - :obj:`False` will not ask for a primal solution and throw away any 

98 primal solution returned by the solver. 

99 """, None), 

100 

101 ("duals", bool, None, """ 

102 Whether to request a dual solution. 

103 

104 - :obj:`True` will raise an exception if no optimal dual solution is 

105 found. 

106 - :obj:`None` will accept and apply also incomplete, infeasible or 

107 suboptimal dual solutions. 

108 - :obj:`False` will not ask for a dual solution and throw away any 

109 dual solution returned by the solver. 

110 """, None), 

111 

112 ("dualize", bool, False, """ 

113 Whether to dualize the problem as part of the solution strategy. 

114 

115 This can sometimes lead to a significant solution search speedup. 

116 """), 

117 

118 ("assume_conic", bool, True, r""" 

119 Determines how :class:`~picos.constraints.ConicQuadraticConstraint` 

120 instances, which correspond to nonconvex constraints of the form 

121 :math:`x^TQx + p^Tx + q \leq (a^Tx + b)(c^Tx + d)` with 

122 :math:`x^TQx + p^Tx + q` representable as a squared norm, are processed: 

123 

124 - :obj:`True` strengthens them into convex conic constraints by assuming 

125 the additional constraints :math:`a^Tx + b \geq 0` and 

126 :math:`c^Tx + d \geq 0`. 

127 - :obj:`False` takes them verbatim and also considers solutions with 

128 :math:`(a^Tx + b) < 0` or :math:`(c^Tx + d) < 0`. This requires a 

129 solver that accepts nonconvex quadratic constraints. 

130 

131 .. warning:: 

132 

133 :class:`~picos.constraints.ConicQuadraticConstraint` are also used 

134 in the case of :math:`Q = 0`. For instance, :math:`x^2 \geq 1` is 

135 effectively ransformed to :math:`x \geq 1` if this is :obj:`True`. 

136 """), 

137 

138 ("apply_solution", bool, True, """ 

139 Whether to immediately apply the solution returned by a solver to the 

140 problem's variables and constraints. 

141 

142 If multiple solutions are returned by the solver, then the first one 

143 will be applied. If this is ``False``, then solutions can be applied 

144 manually via their :meth:`~.solution.Solution.apply` method. 

145 """), 

146 

147 ("abs_prim_fsb_tol", float, 1e-8, """ 

148 Absolute primal problem feasibility tolerance. 

149 

150 A primal solution is feasible if some norm over the vector of primal 

151 constraint violations is smaller than this value. 

152 

153 :obj:`None` lets the solver use its own default value. 

154 """, lambda tol: tol is None or tol > 0.0), 

155 

156 ("rel_prim_fsb_tol", float, 1e-8, """ 

157 Relative primal problem feasibility tolerance. 

158 

159 Like :ref:`abs_prim_fsb_tol <option_abs_prim_fsb_tol>`, but the norm is 

160 divided by the norm of the constraints' right hand side vector. 

161 

162 If the norm used is some nested norm (e.g. the maximum over the norms of 

163 the equality and inequality violations), then solvers might divide the 

164 inner violation norms by the respective right hand side inner norms (see 

165 e.g. `CVXOPT 

166 <https://cvxopt.org/userguide/coneprog.html#algorithm-parameters>`__). 

167 

168 To prevent that the right hand side vector norm is zero (or small), 

169 solvers would either add some small constant or use a fixed lower bound, 

170 which may be as large as :math:`1`. 

171 

172 :obj:`None` lets the solver use its own default value. 

173 """, lambda tol: tol is None or tol > 0.0), 

174 

175 ("abs_dual_fsb_tol", float, 1e-8, """ 

176 Absolute dual problem feasibility tolerance. 

177 

178 A dual solution is feasible if some norm over the vector of dual 

179 constraint violations is smaller than this value. 

180 

181 Serves as an optimality criterion for the Simplex algorithm. 

182 

183 :obj:`None` lets the solver use its own default value. 

184 """, lambda tol: tol is None or tol > 0.0), 

185 

186 ("rel_dual_fsb_tol", float, 1e-8, """ 

187 Relative dual problem feasibility tolerance. 

188 

189 Like :ref:`abs_dual_fsb_tol <option_abs_dual_fsb_tol>`, but the norm is 

190 divided by the norm of the constraints' right hand side vector. (See 

191 :ref:`rel_prim_fsb_tol <option_rel_prim_fsb_tol>` for exceptions.) 

192 

193 Serves as an optimality criterion for the Simplex algorithm. 

194 

195 :obj:`None` lets the solver use its own default value. 

196 """, lambda tol: tol is None or tol > 0.0), 

197 

198 ("abs_ipm_opt_tol", float, 1e-8, """ 

199 Absolute optimality tolerance for interior point methods. 

200 

201 Depending on the solver, a fesible primal/dual solution pair is 

202 considered optimal if this value upper bounds either 

203 

204 - the absolute difference between the primal and dual objective values, 

205 or 

206 - the violation of the complementary slackness condition. 

207 

208 The violation is computed as some norm over the vector that contains the 

209 products of each constraint's slack with its corresponding dual value. 

210 If the norm is the 1-norm, then the two conditions are equal. Otherwise 

211 they can differ by a factor that depends on the number and type of 

212 constraints. 

213 

214 :obj:`None` lets the solver use its own default value. 

215 """, lambda tol: tol is None or tol > 0.0), 

216 

217 ("rel_ipm_opt_tol", float, 1e-8, """ 

218 Relative optimality tolerance for interior point methods. 

219 

220 Like :ref:`abs_ipm_opt_tol <option_abs_ipm_opt_tol>`, but the 

221 suboptimality measure is divided by a convex combination of the absolute 

222 primal and dual objective function values. 

223 

224 :obj:`None` lets the solver use its own default value. 

225 """, lambda tol: tol is None or tol > 0.0), 

226 

227 ("abs_bnb_opt_tol", float, 1e-6, """ 

228 Absolute optimality tolerance for branch-and-bound solution strategies 

229 to mixed integer problems. 

230 

231 A solution is optimal if the absolute difference between the objective 

232 function value of the current best integer solution and the current best 

233 bound obtained from a continuous relaxation is smaller than this value. 

234 

235 :obj:`None` lets the solver use its own default value. 

236 """, lambda tol: tol is None or tol > 0.0), 

237 

238 ("rel_bnb_opt_tol", float, 1e-4, """ 

239 Relative optimality tolerance for branch-and-bound solution strategies 

240 to mixed integer problems. 

241 

242 Like :ref:`abs_bnb_opt_tol <option_abs_bnb_opt_tol>`, but the difference 

243 is divided by a convex combination of the absolute values of the two 

244 objective function values. 

245 

246 :obj:`None` lets the solver use its own default value. 

247 """, lambda tol: tol is None or tol > 0.0), 

248 

249 ("integrality_tol", float, 1e-5, r""" 

250 Integrality tolerance. 

251 

252 A number :math:`x \in \mathbb{R}` is considered integral if 

253 :math:`\min_{z \in \mathbb{Z}}{|x - z|}` is at most this value. 

254 

255 :obj:`None` lets the solver use its own default value. 

256 """, lambda tol: tol is None or (tol > 0.0 and tol < 0.5)), 

257 

258 ("markowitz_tol", float, None, """ 

259 Markowitz threshold used in the Simplex algorithm. 

260 

261 :obj:`None` lets the solver use its own default value. 

262 """, lambda tol: tol is None or (tol > 0.0 and tol < 1.0)), 

263 

264 ("max_iterations", int, None, """ 

265 Maximum number of iterations allowed for iterative solution strategies. 

266 

267 :obj:`None` means no limit. 

268 """, None), 

269 

270 ("max_fsb_nodes", int, None, """ 

271 Maximum number of feasible solution nodes visited for branch-and-bound 

272 solution strategies. 

273 

274 :obj:`None` means no limit. 

275 

276 .. note:: 

277 

278 If you want to obtain all feasible solutions that the solver 

279 encountered, use the :ref:`pool_size <option_pool_size>` option. 

280 """, None), 

281 

282 ("timelimit", int, None, """ 

283 Maximum number of seconds spent searching for a solution. 

284 

285 :obj:`None` means no limit. 

286 """, None), 

287 

288 ("lp_root_method", str, None, """ 

289 Algorithm used to solve continuous linear problems, including the root 

290 relaxation of mixed integer problems. 

291 

292 - :obj:`None` lets PICOS or the solver select it for you. 

293 - ``"psimplex"`` for Primal Simplex. 

294 - ``"dsimplex"`` for Dual Simplex. 

295 - ``"interior"`` for the interior point method. 

296 """, lambda value: value in (None, "psimplex", "dsimplex", "interior")), 

297 

298 ("lp_node_method", str, None, """ 

299 Algorithm used to solve continuous linear problems at non-root nodes of 

300 the branching tree built when solving mixed integer programs. 

301 

302 - :obj:`None` lets PICOS or the solver select it for you. 

303 - ``"psimplex"`` for Primal Simplex. 

304 - ``"dsimplex"`` for Dual Simplex. 

305 - ``"interior"`` for the interior point method. 

306 """, lambda value: value in (None, "psimplex", "dsimplex", "interior")), 

307 

308 ("treememory", int, None, """ 

309 Bound on the memory used by the branch-and-bound tree, in Megabytes. 

310 

311 :obj:`None` means no limit. 

312 """, None), 

313 

314 ("pool_size", int, None, """ 

315 Maximum number of mixed integer feasible solutions returned. 

316 

317 If this is not :obj:`None`, :meth:`~.problem.Problem.solve` 

318 returns a list of :class:`~.solution.Solution` objects instead of just a 

319 single one. 

320 

321 :obj:`None` lets the solver return only the best solution. 

322 """, lambda value: value is None or value >= 1), 

323 

324 ("pool_rel_gap", float, None, """ 

325 Discards solutions from the :ref:`solution pool <option_pool_size>` as 

326 soon as a better solution is found that beats it by the given relative 

327 objective function gap. 

328 

329 :obj:`None` is the solver's choice, which may be *never discard*. 

330 """, None), 

331 

332 ("pool_abs_gap", float, None, """ 

333 Discards solutions from the :ref:`solution pool <option_pool_size>` as 

334 soon as a better solution is found that beats it by the given absolute 

335 objective function gap. 

336 

337 :obj:`None` is the solver's choice, which may be *never discard*. 

338 """, None), 

339 

340 ("hotstart", bool, False, """ 

341 Tells the solver to start from the (partial) solution that is stored in 

342 the :class:`variables <.variables.BaseVariable>` assigned to the 

343 problem."""), 

344 

345 ("verify_prediction", bool, True, """ 

346 Whether PICOS should validate that problem reformulations produce a 

347 problem that matches their predicted outcome. 

348 

349 If a mismatch is detected, a :class:`RuntimeError` is thrown as there is 

350 a chance that it is caused by a bug in the reformulation, which could 

351 affect the correctness of the solution. By disabling this option you are 

352 able to retrieve a correct solution given that the error is only in the 

353 prediction, and given that the solution strategy remains valid for the 

354 actual outcome."""), 

355 

356 ("max_footprints", int, 1024, """ 

357 Maximum number of different predicted problem formulations (footprints) 

358 to consider before deciding on a formulation and solver to use. 

359 

360 :obj:`None` lets PICOS exhaust all reachable problem formulations. 

361 """, None), 

362 

363 # Solver-specific options. 

364 # -------------------------------------------------------------------------- 

365 

366 ("cplex_params", dict, {}, """ 

367 A dictionary of CPLEX parameters to be set after general options are 

368 passed and before the search is started. 

369 

370 For example, ``{"mip.limits.cutpasses": 5}`` limits the number of 

371 cutting plane passes when solving the root node to :math:`5`."""), 

372 

373 ("cplex_vmconfig", str, None, """ 

374 Load a CPLEX virtual machine configuration file. 

375 """, None), 

376 

377 ("cplex_lwr_bnd_limit", float, None, """ 

378 Tells CPLEX to stop MIP optimization if a lower bound below this value 

379 is found. 

380 """, None), 

381 

382 ("cplex_upr_bnd_limit", float, None, """ 

383 Tells CPLEX to stop MIP optimization if an upper bound above this value 

384 is found. 

385 """, None), 

386 

387 ("cplex_bnd_monitor", bool, False, """ 

388 Tells CPLEX to store information about the evolution of the bounds 

389 during the MIP solution search process. At the end of the computation, a 

390 list of triples ``(time, lowerbound, upperbound)`` will be provided in 

391 the field ``bounds_monitor`` of the dictionary returned by 

392 :meth:`~.problem.Problem.solve`. 

393 """), 

394 

395 ("cvxopt_kktsolver", (str, types.FunctionType), None, """ 

396 The KKT solver used by CVXOPT internally. 

397 

398 See `CVXOPT's guide on exploiting structure 

399 <https://cvxopt.org/userguide/coneprog.html#exploiting-structure>`_. 

400 

401 :obj:`None` denotes PICOS' choice: Try first with the faster ``"chol"``, 

402 then with the more reliable ``"ldl"`` solver. 

403 """, None), 

404 

405 ("cvxopt_kktreg", float, 1e-9, """ 

406 The KKT solver regularization term used by CVXOPT internally. 

407 

408 This is an undocumented feature of CVXOPT, see `here 

409 <https://github.com/cvxopt/cvxopt/issues/36#issuecomment-125165634>`_. 

410 

411 End of 2020, this option only affected the LDL KKT solver. 

412 

413 :obj:`None` denotes CVXOPT's default value. 

414 """, None), 

415 

416 ("gurobi_params", dict, {}, """ 

417 A dictionary of Gurobi parameters to be set after general options are 

418 passed and before the search is started. 

419 

420 For example, ``{"NodeLimit": 25}`` limits the number of nodes visited by 

421 the MIP optimizer to :math:`25`."""), 

422 

423 ("gurobi_matint", bool, None, """ 

424 Whether to use Gurobi's matrix interface. 

425 

426 This requires Gurobi 9 or later and SciPy. 

427 

428 :obj:`None` with :data:`~picos.settings.PREFER_GUROBI_MATRIX_INTERFACE` 

429 enabled means *use it if possible*. :obj:`None` with that setting 

430 disabled behaves like :obj:`False`. 

431 """, None), 

432 

433 ("mosek_params", dict, {}, """ 

434 A dictionary of MOSEK (Optimizer) parameters to be set after general 

435 options are passed and before the search is started. 

436 

437 See the `list of MOSEK (Optimizer) 8.1 parameters 

438 <https://docs.mosek.com/8.1/pythonapi/parameters.html>`_."""), 

439 

440 ("mosek_server", str, None, """ 

441 Address of a MOSEK remote optimization server to use. 

442 

443 This option affects both MOSEK (Optimizer) and MOSEK (Fusion). 

444 """, None), 

445 

446 ("mosek_basic_sol", bool, False, """ 

447 Return a basic solution when solving LPs with MOSEK (Optimizer). 

448 """), 

449 

450 ("mskfsn_params", dict, {}, """ 

451 A dictionary of MOSEK (Fusion) parameters to be set after general 

452 options are passed and before the search is started. 

453 

454 See the `list of MOSEK (Fusion) 8.1 parameters 

455 <https://docs.mosek.com/8.1/pythonfusion/parameters.html>`_."""), 

456 

457 ("osqp_params", dict, {}, """ 

458 A dictionary of OSQP parameters to be set after general options are 

459 passed and before the search is started. 

460 

461 See the `list of OSQP parameters 

462 <https://osqp.org/docs/interfaces/solver_settings.html>`_."""), 

463 

464 ("scip_params", dict, {}, """ 

465 A dictionary of SCIP parameters to be set after general options are 

466 passed and before the search is started. 

467 

468 For example, ``{"lp/threads": 4}`` sets the number of threads to solve 

469 LPs with to :math:`4`."""), 

470] 

471"""The table of available solver options. 

472 

473Each entry is a tuple representing a single solver option. The tuple's entries 

474are, in order: 

475 

476- Name of the option. Must be a valid Python attribute name. 

477- The option's argument type. Will be cast on any argument that is not already 

478 an instance of the type, except for :obj:`None`. 

479- The option's default value. Must already be of the proper type, or 

480 :obj:`None`, and must pass the optional check. 

481- The option's description, which is used as part of the docstring of 

482 :class:`Options`. In the case of a multi-line text, leading and trailing 

483 empty lines as well as the overall indentation are ignored. 

484- Optional: A boolean function used on every argument that passes the type 

485 conversion (so either an argument of the proper type, or :obj:`None`). If the 

486 function returns ``False``, then the argument is rejected. The default 

487 function rejects exactly :obj:`None`. Supplying :obj:`None` instead of a 

488 function accepts all arguments (in particular, accepts :obj:`None`). 

489""" 

490 

491# Add per-solver options. 

492for name, solver in all_solvers().items(): 

493 OPTIONS.append(("penalty_{}".format(name), float, solver.default_penalty(), 

494 """ 

495 Penalty for using the {} solver. 

496 

497 If solver :math:`A` has a penalty of :math:`p` and solver :math:`B` has 

498 a larger penality of :math:`p + x`, then :math:`B` is be chosen over 

499 :math:`A` only if the problem as passed to :math:`A` would be 

500 :math:`10^x` times larger as when passed to :math:`B`. 

501 """.format(name.upper()))) 

502 

503del name, solver 

504 

505OPTIONS = sorted(OPTIONS) 

506 

507 

508class Option(): 

509 """Optimization solver option. 

510 

511 A single option that affects how a :class:`~.problem.Problem` is solved. 

512 

513 An initial instance of this class is built from each entry of the 

514 :data:`OPTIONS` table to obtain the :data:`OPTION_OBJS` tuple. 

515 """ 

516 

517 # Define __new__ in addition to __init__ so that copy can bypass __init__. 

518 def __new__(cls, *args, **kwargs): 

519 """Create a blank :class:`Option` to be filled in by :meth:`copy`.""" 

520 return super(Option, cls).__new__(cls) 

521 

522 def __init__(self, name, argType, default, description, 

523 check=(lambda x: x is not None)): 

524 """Initialize an :class:`Option`. 

525 

526 See :data:`OPTIONS`. 

527 """ 

528 assert default is None or isinstance(default, argType) 

529 assert check is None or check(default) 

530 

531 self.name = name 

532 self.argType = argType 

533 self.default = default 

534 self._value = default 

535 self.description = self._normalize_description(description) 

536 self.check = check 

537 

538 def _normalize_description(self, description): 

539 lines = description.splitlines() 

540 notSpace = [n for n, line in enumerate(lines) if line.strip()] 

541 if not notSpace: 

542 return "" 

543 first, last = min(notSpace), max(notSpace) 

544 i = len(lines[first]) - len(lines[first].lstrip()) 

545 return "\n".join(line[i:].rstrip() for line in lines[first:last+1]) 

546 

547 def _set_value(self, value): 

548 if value is not None and not isinstance(value, self.argType): 

549 if isinstance(self.argType, type): 

550 try: 

551 value = self.argType(value) 

552 except Exception as error: 

553 raise TypeError("Failed to convert argument {} to option " 

554 "'{}' to type {}.".format(repr(value), self.name, 

555 self.argType.__name__)) from error 

556 else: 

557 assert isinstance(self.argType, (tuple, list)) 

558 assert all(isinstance(t, type) for t in self.argType) 

559 

560 raise TypeError("Argument {} to option '{}' does not match " 

561 "permissible types {}.".format(repr(value), self.name, 

562 ", ".join(t.__name__ for t in self.argType))) 

563 

564 if self.check is not None and not self.check(value): 

565 raise ValueError("The option '{}' does not accept the value {}." 

566 .format(self.name, repr(value))) 

567 

568 self._value = value 

569 

570 value = property(lambda self: self._value, _set_value) 

571 

572 def reset(self): 

573 """Reset the option to its default value.""" 

574 self.value = self.default 

575 

576 def is_default(self): 

577 """Whether the option has its default value.""" 

578 return self.value == self.default 

579 

580 def copy(self): 

581 """Return an independent copy of the option.""" 

582 theCopy = self.__class__.__new__(self.__class__) 

583 theCopy.name = self.name 

584 theCopy.argType = self.argType 

585 theCopy.default = self.default 

586 theCopy._value = self._value 

587 theCopy.description = self.description 

588 theCopy.check = self.check 

589 return theCopy 

590 

591 

592OPTION_OBJS = tuple(Option(*args) for args in OPTIONS) 

593"""The initial solver options as :class:`Option` objects.""" 

594 

595 

596def _tablerow(option, indentaion=0): 

597 """Return a reST list-table row describing an :class:`Option`.""" 

598 spaces = " "*indentaion 

599 return ( 

600 "{}- * {{0}}\n" 

601 "{} * ``{{1}}``\n" 

602 "{} * .. _option_{{0}}:\n\n" 

603 "{} {{2}}" 

604 ).format( 

605 *(4*(spaces,))).format(option.name, repr(option.default), 

606 "\n{} ".format(spaces).join(option.description.splitlines())) 

607 

608 

609def _jumplabel(option): 

610 """Return a reStructuredText jumplabel describing an :class:`Option`.""" 

611 return ":ref:`{0} <option_{0}>`".format(option.name) 

612 

613 

614class Options(): 

615 """Collection of optimization solver options. 

616 

617 A collection of options that affect how a :class:`~.problem.Problem` is 

618 solved. :attr:`Problem.options <.problem.Problem.options>` is an instance of 

619 this class. 

620 

621 The options can be accessed as an attribute or as an item. The latter 

622 approach supports Unix shell-style wildcard characters: 

623 

624 >>> import picos 

625 >>> P = picos.Problem() 

626 >>> P.options.verbosity = 2 

627 >>> P.options["primals"] = False 

628 >>> # Set all absolute tolerances at once. 

629 >>> P.options["abs_*_tol"] = 1e-6 

630 

631 There are two corresponding ways to reset an option to its default value: 

632 

633 >>> del P.options.verbosity 

634 >>> P.options.reset("primals", "*_tol") 

635 

636 Options can also be passed as a keyword argument sequence when the 

637 :class:`Problem <picos.Problem>` is created and whenever a solution is 

638 searched: 

639 

640 >>> # Use default options except for verbosity. 

641 >>> P = picos.Problem(verbosity = 1) 

642 >>> x = P.add_variable("x", lower = 0); P.set_objective("min", x) 

643 >>> # Only for the next search: Don't be verbose anyway. 

644 >>> solution = P.solve(solver = "cvxopt", verbosity = 0) 

645 """ 

646 

647 # Document the individual options. 

648 __doc__ += \ 

649 """ 

650 .. rubric:: Available Options 

651 

652 Jump to option: ➥\xa0{} 

653 

654 .. list-table:: 

655 :header-rows: 1 

656 :widths: 10 10 80 

657 

658 - * Option 

659 * Default 

660 * Description 

661 """.format(" ➥\xa0".join(_jumplabel(option) for option in OPTION_OBJS)) \ 

662 .rstrip() + "\n" + "\n".join(_tablerow(option, 6) for option in OPTION_OBJS) 

663 

664 # Define __new__ in addition to __init__ so that 

665 # 1. __init__ does not take the static default options as an argument, 

666 # hiding them from the user and the documentation while 

667 # 2. Options.copy can still bypass copying the default options (by bypassing 

668 # __init__) so that options aren't copied twice. 

669 def __new__(cls, *args, **kwargs): 

670 """Create an empty options set.""" 

671 instance = super(Options, cls).__new__(cls) 

672 # Options overwrites __setattr__, so we need to call object.__setattr__. 

673 super(Options, cls).__setattr__(instance, "_options", {}) 

674 return instance 

675 

676 def __init__(self, **options): 

677 """Create a default option set and set the given options on top.""" 

678 for option in OPTION_OBJS: 

679 self._options[option.name] = option.copy() 

680 

681 self.update(**options) 

682 

683 def __str__(self): 

684 defaults = sorted( 

685 (o for o in self._options.values() if o.is_default()), 

686 key=(lambda o: o.name)) 

687 modified = sorted( 

688 (o for o in self._options.values() if not o.is_default()), 

689 key=(lambda o: o.name)) 

690 

691 nameLength = max(len(o.name) for o in self._options.values()) 

692 valueLength = max(len(str(o.value)) for o in self._options.values()) 

693 

694 string = "" 

695 

696 if modified: 

697 defaultLength = max(len(str(o.default)) for o in modified) 

698 

699 string += "Modified solver options:\n" + "\n".join(( 

700 " {{:{}}} = {{:{}}} (default: {{:{}}})".format( 

701 nameLength, valueLength, defaultLength 

702 ).format( 

703 option.name, str(option.value), str(option.default)) 

704 for num, option in enumerate(modified))) 

705 

706 if defaults: 

707 if modified: 

708 string += "\n\n" 

709 

710 string += "Default solver options:\n" + "\n".join(( 

711 " {{:{}}} = {{}}".format(nameLength).format( 

712 option.name, str(option.value)) 

713 for num, option in enumerate(defaults))) 

714 

715 return string 

716 

717 def __eq__(self, other): 

718 """Report whether two sets of options are equal.""" 

719 if self is other: 

720 return True 

721 

722 for name in self._options: 

723 if self._options[name].value != other._options[name].value: 

724 return False 

725 

726 return True 

727 

728 def _fuzzy(returnsSomething): 

729 """Allow wildcards in option names.""" 

730 def decorator(method): 

731 def wrapper(self, pattern, *extraArgs): 

732 if any(char in pattern for char in "*?[!]"): 

733 matching = fnmatch.filter(self._options.keys(), pattern) 

734 

735 if not matching: 

736 raise LookupError("No option matches '{}'." 

737 .format(pattern)) 

738 

739 if returnsSomething: 

740 return {name: method(self, name, *extraArgs) 

741 for name in matching} 

742 else: 

743 for name in matching: 

744 method(self, name, *extraArgs) 

745 else: 

746 if returnsSomething: 

747 return method(self, pattern, *extraArgs) 

748 else: 

749 method(self, pattern, *extraArgs) 

750 return wrapper 

751 return decorator 

752 

753 @_fuzzy(True) 

754 def __getattr__(self, name): 

755 if name in self._options: 

756 return self._options[name].value 

757 else: 

758 raise AttributeError("Unknown option '{}'.".format(name)) 

759 

760 @_fuzzy(False) 

761 def __setattr__(self, name, value): 

762 if name in self._options: 

763 self._options[name].value = value 

764 else: 

765 raise AttributeError("Unknown option '{}'.".format(name)) 

766 

767 @_fuzzy(False) 

768 def __delattr__(self, name): 

769 if name in self._options: 

770 self._options[name].reset() 

771 else: 

772 raise AttributeError("Unknown option '{}'.".format(name)) 

773 

774 @_fuzzy(True) 

775 def __getitem__(self, name): 

776 if name in self._options: 

777 return self._options[name].value 

778 else: 

779 raise LookupError("Unknown option '{}'.".format(name)) 

780 

781 @_fuzzy(False) 

782 def __setitem__(self, name, value): 

783 if name in self._options: 

784 self._options[name].value = value 

785 else: 

786 raise LookupError("Unknown option '{}'.".format(name)) 

787 

788 def __contains__(self, name): 

789 return name in self._options 

790 

791 def __dir__(self): 

792 optionNames = [name for name in self._options.keys()] 

793 list_ = super(Options, self).__dir__() + optionNames 

794 return sorted(list_) 

795 

796 def copy(self): 

797 """Return an independent copy of the current options set.""" 

798 theCopy = self.__class__.__new__(self.__class__) 

799 for option in self._options.values(): 

800 theCopy._options[option.name] = option.copy() 

801 return theCopy 

802 

803 def update(self, **options): 

804 """Set multiple options at once. 

805 

806 This method is called with the keyword arguments supplied to the 

807 :class:`Options` constructor, so the following two are the same: 

808 

809 >>> import picos 

810 >>> a = picos.Options(verbosity = 1, primals = False) 

811 >>> b = picos.Options() 

812 >>> b.update(verbosity = 1, primals = False) 

813 >>> a == b 

814 True 

815 

816 :param options: A parameter sequence of options to set. 

817 """ 

818 for key, val in options.items(): 

819 self[key] = val 

820 

821 def updated(self, **options): 

822 """Return a modified copy.""" 

823 theCopy = self.copy() 

824 if options: 

825 theCopy.update(**options) 

826 return theCopy 

827 

828 def self_or_updated(self, **options): 

829 """Return either a modified copy or self, depending on given options.""" 

830 if options: 

831 theCopy = self.copy() 

832 theCopy.update(**options) 

833 return theCopy 

834 else: 

835 return self 

836 

837 @_fuzzy(False) 

838 def _reset_single(self, name): 

839 self._options[name].reset() 

840 

841 def reset(self, *options): 

842 """Reset all or a selection of options to their default values. 

843 

844 :param options: The names of the options to reset, may contain wildcard 

845 characters. If no name is given, all options are reset. 

846 """ 

847 if options: 

848 for name in options: 

849 self._reset_single(name) 

850 else: 

851 for option in self._options.values(): 

852 option.reset() 

853 

854 @_fuzzy(True) 

855 def _help_single(self, name): 

856 option = self._options[name] 

857 return ( 

858 "Option: {}\n" 

859 "Default: {}\n" 

860 "\n {}" 

861 ).format(option.name, str(option.default), 

862 "\n ".join(option.description.splitlines())) 

863 

864 def help(self, *options): 

865 """Print text describing selected options. 

866 

867 :param options: The names of the options to describe, may contain 

868 wildcard characters. 

869 """ 

870 for i, name in enumerate(options): 

871 if i != 0: 

872 print("\n\n") 

873 retval = self._help_single(name) 

874 if isinstance(retval, str): 

875 print(retval) 

876 else: 

877 assert isinstance(retval, dict) 

878 print("\n\n".join(retval.values())) 

879 

880 @property 

881 def nondefaults(self): 

882 """A dictionary mapping option names to nondefault values. 

883 

884 :Example: 

885 

886 >>> from picos import Options 

887 >>> o = Options() 

888 >>> o.verbosity = 2 

889 >>> o.nondefaults 

890 {'verbosity': 2} 

891 >>> Options(**o.nondefaults) == o 

892 True 

893 """ 

894 return {name: option._value for name, option in self._options.items() 

895 if option._value != option.default} 

896 

897 

898# -------------------------------------- 

899__all__ = api_end(_API_START, globals())