Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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_lwr_bnd_limit", float, None, """ 

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

375 is found. 

376 """, None), 

377 

378 ("cplex_upr_bnd_limit", float, None, """ 

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

380 is found. 

381 """, None), 

382 

383 ("cplex_bnd_monitor", bool, False, """ 

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

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

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

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

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

389 """), 

390 

391 ("cvxopt_kktsolver", (str, types.FunctionType), "ldl", """ 

392 The KKT solver used by CVXOPT internally. 

393 

394 See `CVXOPT's guide on exploiting structure 

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

396 

397 :obj:`None` denotes CVXOPT's default KKT solver. 

398 """, None), 

399 

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

401 The KKT solver regularization term used by CVXOPT internally. 

402 

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

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

405 

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

407 

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

409 """, None), 

410 

411 ("gurobi_params", dict, {}, """ 

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

413 passed and before the search is started. 

414 

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

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

417 

418 ("mosek_params", dict, {}, """ 

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

420 options are passed and before the search is started. 

421 

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

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

424 

425 ("mskfsn_params", dict, {}, """ 

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

427 options are passed and before the search is started. 

428 

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

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

431 

432 ("scip_params", dict, {}, """ 

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

434 passed and before the search is started. 

435 

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

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

438] 

439"""The table of available solver options. 

440 

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

442are, in order: 

443 

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

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

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

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

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

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

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

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

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

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

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

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

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

457""" 

458 

459# Add per-solver options. 

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

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

462 """ 

463 Penalty for using the {} solver. 

464 

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

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

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

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

469 """.format(name.upper()))) 

470 

471del name, solver 

472 

473OPTIONS = sorted(OPTIONS) 

474 

475 

476class Option(): 

477 """Optimization solver option. 

478 

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

480 

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

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

483 """ 

484 

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

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

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

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

489 

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

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

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

493 

494 See :data:`OPTIONS`. 

495 """ 

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

497 assert check is None or check(default) 

498 

499 self.name = name 

500 self.argType = argType 

501 self.default = default 

502 self._value = default 

503 self.description = self._normalize_description(description) 

504 self.check = check 

505 

506 def _normalize_description(self, description): 

507 lines = description.splitlines() 

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

509 if not notSpace: 

510 return "" 

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

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

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

514 

515 def _set_value(self, value): 

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

517 if isinstance(self.argType, type): 

518 try: 

519 value = self.argType(value) 

520 except Exception as error: 

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

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

523 self.argType.__name__)) from error 

524 else: 

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

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

527 

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

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

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

531 

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

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

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

535 

536 self._value = value 

537 

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

539 

540 def reset(self): 

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

542 self.value = self.default 

543 

544 def is_default(self): 

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

546 return self.value == self.default 

547 

548 def copy(self): 

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

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

551 theCopy.name = self.name 

552 theCopy.argType = self.argType 

553 theCopy.default = self.default 

554 theCopy._value = self._value 

555 theCopy.description = self.description 

556 theCopy.check = self.check 

557 return theCopy 

558 

559 

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

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

562 

563 

564def _tablerow(option, indentaion=0): 

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

566 spaces = " "*indentaion 

567 return ( 

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

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

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

571 "{} {{2}}" 

572 ).format( 

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

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

575 

576 

577def _jumplabel(option): 

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

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

580 

581 

582class Options(): 

583 """Collection of optimization solver options. 

584 

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

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

587 this class. 

588 

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

590 approach supports Unix shell-style wildcard characters: 

591 

592 >>> import picos 

593 >>> P = picos.Problem() 

594 >>> P.options.verbosity = 2 

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

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

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

598 

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

600 

601 >>> del P.options.verbosity 

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

603 

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

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

606 searched: 

607 

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

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

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

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

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

613 """ 

614 

615 # Document the individual options. 

616 __doc__ += \ 

617 """ 

618 .. rubric:: Available Options 

619 

620 Jump to option: ➥\xa0{} 

621 

622 .. list-table:: 

623 :header-rows: 1 

624 :widths: 10 10 80 

625 

626 - * Option 

627 * Default 

628 * Description 

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

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

631 

632 # Define __new__ in addition to __init__ so that 

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

634 # hiding them from the user and the documentation while 

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

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

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

638 """Create an empty options set.""" 

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

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

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

642 return instance 

643 

644 def __init__(self, **options): 

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

646 for option in OPTION_OBJS: 

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

648 

649 self.update(**options) 

650 

651 def __str__(self): 

652 defaults = sorted( 

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

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

655 modified = sorted( 

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

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

658 

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

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

661 

662 string = "" 

663 

664 if modified: 

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

666 

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

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

669 nameLength, valueLength, defaultLength 

670 ).format( 

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

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

673 

674 if defaults: 

675 if modified: 

676 string += "\n\n" 

677 

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

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

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

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

682 

683 return string 

684 

685 def __eq__(self, other): 

686 if self is other: 

687 return True 

688 

689 for name in self._options: 

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

691 return False 

692 

693 return True 

694 

695 def _fuzzy(returnsSomething): 

696 """Allow wildcards in option names.""" 

697 def decorator(method): 

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

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

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

701 

702 if not matching: 

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

704 .format(pattern)) 

705 

706 if returnsSomething: 

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

708 for name in matching} 

709 else: 

710 for name in matching: 

711 method(self, name, *extraArgs) 

712 else: 

713 if returnsSomething: 

714 return method(self, pattern, *extraArgs) 

715 else: 

716 method(self, pattern, *extraArgs) 

717 return wrapper 

718 return decorator 

719 

720 @_fuzzy(True) 

721 def __getattr__(self, name): 

722 if name in self._options: 

723 return self._options[name].value 

724 else: 

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

726 

727 @_fuzzy(False) 

728 def __setattr__(self, name, value): 

729 if name in self._options: 

730 self._options[name].value = value 

731 else: 

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

733 

734 @_fuzzy(False) 

735 def __delattr__(self, name): 

736 if name in self._options: 

737 self._options[name].reset() 

738 else: 

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

740 

741 @_fuzzy(True) 

742 def __getitem__(self, name): 

743 if name in self._options: 

744 return self._options[name].value 

745 else: 

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

747 

748 @_fuzzy(False) 

749 def __setitem__(self, name, value): 

750 if name in self._options: 

751 self._options[name].value = value 

752 else: 

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

754 

755 def __contains__(self, name): 

756 return name in self._options 

757 

758 def __dir__(self): 

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

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

761 return sorted(list_) 

762 

763 def copy(self): 

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

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

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

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

768 return theCopy 

769 

770 def update(self, **options): 

771 """Set multiple options at once. 

772 

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

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

775 

776 >>> import picos 

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

778 >>> b = picos.Options() 

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

780 >>> a == b 

781 True 

782 

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

784 """ 

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

786 self[key] = val 

787 

788 def updated(self, **options): 

789 """Return a modified copy.""" 

790 theCopy = self.copy() 

791 if options: 

792 theCopy.update(**options) 

793 return theCopy 

794 

795 def self_or_updated(self, **options): 

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

797 if options: 

798 theCopy = self.copy() 

799 theCopy.update(**options) 

800 return theCopy 

801 else: 

802 return self 

803 

804 @_fuzzy(False) 

805 def _reset_single(self, name): 

806 self._options[name].reset() 

807 

808 def reset(self, *options): 

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

810 

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

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

813 """ 

814 if options: 

815 for name in options: 

816 self._reset_single(name) 

817 else: 

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

819 option.reset() 

820 

821 @_fuzzy(True) 

822 def _help_single(self, name): 

823 option = self._options[name] 

824 return ( 

825 "Option: {}\n" 

826 "Default: {}\n" 

827 "\n {}" 

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

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

830 

831 def help(self, *options): 

832 """Print text describing selected options. 

833 

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

835 wildcard characters. 

836 """ 

837 for i, name in enumerate(options): 

838 if i != 0: 

839 print("\n\n") 

840 retval = self._help_single(name) 

841 if isinstance(retval, str): 

842 print(retval) 

843 else: 

844 assert isinstance(retval, dict) 

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

846 

847 @property 

848 def nondefaults(self): 

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

850 

851 :Example: 

852 

853 >>> from picos import Options 

854 >>> o = Options() 

855 >>> o.verbosity = 2 

856 >>> o.nondefaults 

857 {'verbosity': 2} 

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

859 True 

860 """ 

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

862 if option._value != option.default} 

863 

864 

865# -------------------------------------- 

866__all__ = api_end(_API_START, globals())