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 ("mosek_server", str, None, """ 

426 Address of a MOSEK remote optimization server to use. 

427 

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

429 """, None), 

430 

431 ("mskfsn_params", dict, {}, """ 

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

433 options are passed and before the search is started. 

434 

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

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

437 

438 ("scip_params", dict, {}, """ 

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

440 passed and before the search is started. 

441 

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

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

444] 

445"""The table of available solver options. 

446 

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

448are, in order: 

449 

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

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

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

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

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

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

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

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

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

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

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

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

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

463""" 

464 

465# Add per-solver options. 

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

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

468 """ 

469 Penalty for using the {} solver. 

470 

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

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

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

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

475 """.format(name.upper()))) 

476 

477del name, solver 

478 

479OPTIONS = sorted(OPTIONS) 

480 

481 

482class Option(): 

483 """Optimization solver option. 

484 

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

486 

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

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

489 """ 

490 

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

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

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

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

495 

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

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

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

499 

500 See :data:`OPTIONS`. 

501 """ 

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

503 assert check is None or check(default) 

504 

505 self.name = name 

506 self.argType = argType 

507 self.default = default 

508 self._value = default 

509 self.description = self._normalize_description(description) 

510 self.check = check 

511 

512 def _normalize_description(self, description): 

513 lines = description.splitlines() 

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

515 if not notSpace: 

516 return "" 

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

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

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

520 

521 def _set_value(self, value): 

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

523 if isinstance(self.argType, type): 

524 try: 

525 value = self.argType(value) 

526 except Exception as error: 

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

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

529 self.argType.__name__)) from error 

530 else: 

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

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

533 

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

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

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

537 

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

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

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

541 

542 self._value = value 

543 

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

545 

546 def reset(self): 

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

548 self.value = self.default 

549 

550 def is_default(self): 

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

552 return self.value == self.default 

553 

554 def copy(self): 

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

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

557 theCopy.name = self.name 

558 theCopy.argType = self.argType 

559 theCopy.default = self.default 

560 theCopy._value = self._value 

561 theCopy.description = self.description 

562 theCopy.check = self.check 

563 return theCopy 

564 

565 

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

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

568 

569 

570def _tablerow(option, indentaion=0): 

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

572 spaces = " "*indentaion 

573 return ( 

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

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

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

577 "{} {{2}}" 

578 ).format( 

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

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

581 

582 

583def _jumplabel(option): 

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

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

586 

587 

588class Options(): 

589 """Collection of optimization solver options. 

590 

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

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

593 this class. 

594 

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

596 approach supports Unix shell-style wildcard characters: 

597 

598 >>> import picos 

599 >>> P = picos.Problem() 

600 >>> P.options.verbosity = 2 

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

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

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

604 

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

606 

607 >>> del P.options.verbosity 

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

609 

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

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

612 searched: 

613 

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

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

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

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

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

619 """ 

620 

621 # Document the individual options. 

622 __doc__ += \ 

623 """ 

624 .. rubric:: Available Options 

625 

626 Jump to option: ➥\xa0{} 

627 

628 .. list-table:: 

629 :header-rows: 1 

630 :widths: 10 10 80 

631 

632 - * Option 

633 * Default 

634 * Description 

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

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

637 

638 # Define __new__ in addition to __init__ so that 

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

640 # hiding them from the user and the documentation while 

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

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

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

644 """Create an empty options set.""" 

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

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

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

648 return instance 

649 

650 def __init__(self, **options): 

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

652 for option in OPTION_OBJS: 

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

654 

655 self.update(**options) 

656 

657 def __str__(self): 

658 defaults = sorted( 

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

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

661 modified = sorted( 

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

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

664 

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

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

667 

668 string = "" 

669 

670 if modified: 

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

672 

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

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

675 nameLength, valueLength, defaultLength 

676 ).format( 

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

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

679 

680 if defaults: 

681 if modified: 

682 string += "\n\n" 

683 

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

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

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

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

688 

689 return string 

690 

691 def __eq__(self, other): 

692 if self is other: 

693 return True 

694 

695 for name in self._options: 

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

697 return False 

698 

699 return True 

700 

701 def _fuzzy(returnsSomething): 

702 """Allow wildcards in option names.""" 

703 def decorator(method): 

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

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

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

707 

708 if not matching: 

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

710 .format(pattern)) 

711 

712 if returnsSomething: 

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

714 for name in matching} 

715 else: 

716 for name in matching: 

717 method(self, name, *extraArgs) 

718 else: 

719 if returnsSomething: 

720 return method(self, pattern, *extraArgs) 

721 else: 

722 method(self, pattern, *extraArgs) 

723 return wrapper 

724 return decorator 

725 

726 @_fuzzy(True) 

727 def __getattr__(self, name): 

728 if name in self._options: 

729 return self._options[name].value 

730 else: 

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

732 

733 @_fuzzy(False) 

734 def __setattr__(self, name, value): 

735 if name in self._options: 

736 self._options[name].value = value 

737 else: 

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

739 

740 @_fuzzy(False) 

741 def __delattr__(self, name): 

742 if name in self._options: 

743 self._options[name].reset() 

744 else: 

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

746 

747 @_fuzzy(True) 

748 def __getitem__(self, name): 

749 if name in self._options: 

750 return self._options[name].value 

751 else: 

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

753 

754 @_fuzzy(False) 

755 def __setitem__(self, name, value): 

756 if name in self._options: 

757 self._options[name].value = value 

758 else: 

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

760 

761 def __contains__(self, name): 

762 return name in self._options 

763 

764 def __dir__(self): 

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

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

767 return sorted(list_) 

768 

769 def copy(self): 

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

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

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

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

774 return theCopy 

775 

776 def update(self, **options): 

777 """Set multiple options at once. 

778 

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

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

781 

782 >>> import picos 

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

784 >>> b = picos.Options() 

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

786 >>> a == b 

787 True 

788 

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

790 """ 

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

792 self[key] = val 

793 

794 def updated(self, **options): 

795 """Return a modified copy.""" 

796 theCopy = self.copy() 

797 if options: 

798 theCopy.update(**options) 

799 return theCopy 

800 

801 def self_or_updated(self, **options): 

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

803 if options: 

804 theCopy = self.copy() 

805 theCopy.update(**options) 

806 return theCopy 

807 else: 

808 return self 

809 

810 @_fuzzy(False) 

811 def _reset_single(self, name): 

812 self._options[name].reset() 

813 

814 def reset(self, *options): 

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

816 

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

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

819 """ 

820 if options: 

821 for name in options: 

822 self._reset_single(name) 

823 else: 

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

825 option.reset() 

826 

827 @_fuzzy(True) 

828 def _help_single(self, name): 

829 option = self._options[name] 

830 return ( 

831 "Option: {}\n" 

832 "Default: {}\n" 

833 "\n {}" 

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

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

836 

837 def help(self, *options): 

838 """Print text describing selected options. 

839 

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

841 wildcard characters. 

842 """ 

843 for i, name in enumerate(options): 

844 if i != 0: 

845 print("\n\n") 

846 retval = self._help_single(name) 

847 if isinstance(retval, str): 

848 print(retval) 

849 else: 

850 assert isinstance(retval, dict) 

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

852 

853 @property 

854 def nondefaults(self): 

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

856 

857 :Example: 

858 

859 >>> from picos import Options 

860 >>> o = Options() 

861 >>> o.verbosity = 2 

862 >>> o.nondefaults 

863 {'verbosity': 2} 

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

865 True 

866 """ 

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

868 if option._value != option.default} 

869 

870 

871# -------------------------------------- 

872__all__ = api_end(_API_START, globals())