|
9 | 9 | # This software is distributed under the 3-clause BSD License.
|
10 | 10 | # ___________________________________________________________________________
|
11 | 11 |
|
| 12 | +import datetime |
12 | 13 | import random
|
13 | 14 | import math
|
14 | 15 | from typing import Type
|
|
17 | 18 | from pyomo import gdp
|
18 | 19 | from pyomo.common.dependencies import attempt_import
|
19 | 20 | import pyomo.common.unittest as unittest
|
| 21 | + |
| 22 | +from pyomo.contrib.solver.common.base import SolverBase |
| 23 | +from pyomo.contrib.solver.common.config import SolverConfig |
| 24 | +from pyomo.contrib.solver.common.factory import SolverFactory |
| 25 | +from pyomo.contrib.solver.solvers.gurobi_persistent import GurobiPersistent |
| 26 | +from pyomo.contrib.solver.solvers.gurobi_direct import GurobiDirect |
| 27 | +from pyomo.contrib.solver.solvers.highs import Highs |
| 28 | +from pyomo.contrib.solver.solvers.ipopt import Ipopt |
20 | 29 | from pyomo.contrib.solver.common.results import (
|
21 | 30 | TerminationCondition,
|
22 | 31 | SolutionStatus,
|
|
27 | 36 | NoSolutionError,
|
28 | 37 | NoReducedCostsError,
|
29 | 38 | )
|
30 |
| -from pyomo.contrib.solver.common.base import SolverBase |
31 |
| -from pyomo.contrib.solver.common.factory import SolverFactory |
32 |
| -from pyomo.contrib.solver.solvers.ipopt import Ipopt |
33 |
| -from pyomo.contrib.solver.solvers.gurobi_persistent import GurobiPersistent |
34 |
| -from pyomo.contrib.solver.solvers.gurobi_direct import GurobiDirect |
35 |
| -from pyomo.contrib.solver.solvers.highs import Highs |
36 | 39 | from pyomo.core.expr.numeric_expr import LinearExpression
|
37 | 40 | from pyomo.core.expr.compare import assertExpressionsEqual
|
38 | 41 |
|
@@ -99,7 +102,7 @@ def test_equality(self, name: str, opt_class: Type[SolverBase], use_presolve: bo
|
99 | 102 | if any(name.startswith(i) for i in nl_solvers_set):
|
100 | 103 | if use_presolve:
|
101 | 104 | raise unittest.SkipTest(
|
102 |
| - f'cannot yet get duals if NLWriter presolve is on' |
| 105 | + 'cannot yet get duals if NLWriter presolve is on' |
103 | 106 | )
|
104 | 107 | else:
|
105 | 108 | opt.config.writer_config.linear_presolve = False
|
@@ -153,7 +156,7 @@ def test_inequality(
|
153 | 156 | if any(name.startswith(i) for i in nl_solvers_set):
|
154 | 157 | if use_presolve:
|
155 | 158 | raise unittest.SkipTest(
|
156 |
| - f'cannot yet get duals if NLWriter presolve is on' |
| 159 | + 'cannot yet get duals if NLWriter presolve is on' |
157 | 160 | )
|
158 | 161 | else:
|
159 | 162 | opt.config.writer_config.linear_presolve = False
|
@@ -213,7 +216,7 @@ def test_bounds(self, name: str, opt_class: Type[SolverBase], use_presolve: bool
|
213 | 216 | if any(name.startswith(i) for i in nl_solvers_set):
|
214 | 217 | if use_presolve:
|
215 | 218 | raise unittest.SkipTest(
|
216 |
| - f'cannot yet get duals if NLWriter presolve is on' |
| 219 | + 'cannot yet get duals if NLWriter presolve is on' |
217 | 220 | )
|
218 | 221 | else:
|
219 | 222 | opt.config.writer_config.linear_presolve = False
|
@@ -268,7 +271,7 @@ def test_range(self, name: str, opt_class: Type[SolverBase], use_presolve: bool)
|
268 | 271 | if any(name.startswith(i) for i in nl_solvers_set):
|
269 | 272 | if use_presolve:
|
270 | 273 | raise unittest.SkipTest(
|
271 |
| - f'cannot yet get duals if NLWriter presolve is on' |
| 274 | + 'cannot yet get duals if NLWriter presolve is on' |
272 | 275 | )
|
273 | 276 | else:
|
274 | 277 | opt.config.writer_config.linear_presolve = False
|
@@ -322,7 +325,7 @@ def test_equality_max(
|
322 | 325 | if any(name.startswith(i) for i in nl_solvers_set):
|
323 | 326 | if use_presolve:
|
324 | 327 | raise unittest.SkipTest(
|
325 |
| - f'cannot yet get duals if NLWriter presolve is on' |
| 328 | + 'cannot yet get duals if NLWriter presolve is on' |
326 | 329 | )
|
327 | 330 | else:
|
328 | 331 | opt.config.writer_config.linear_presolve = False
|
@@ -376,7 +379,7 @@ def test_inequality_max(
|
376 | 379 | if any(name.startswith(i) for i in nl_solvers_set):
|
377 | 380 | if use_presolve:
|
378 | 381 | raise unittest.SkipTest(
|
379 |
| - f'cannot yet get duals if NLWriter presolve is on' |
| 382 | + 'cannot yet get duals if NLWriter presolve is on' |
380 | 383 | )
|
381 | 384 | else:
|
382 | 385 | opt.config.writer_config.linear_presolve = False
|
@@ -438,7 +441,7 @@ def test_bounds_max(
|
438 | 441 | if any(name.startswith(i) for i in nl_solvers_set):
|
439 | 442 | if use_presolve:
|
440 | 443 | raise unittest.SkipTest(
|
441 |
| - f'cannot yet get duals if NLWriter presolve is on' |
| 444 | + 'cannot yet get duals if NLWriter presolve is on' |
442 | 445 | )
|
443 | 446 | else:
|
444 | 447 | opt.config.writer_config.linear_presolve = False
|
@@ -495,7 +498,7 @@ def test_range_max(
|
495 | 498 | if any(name.startswith(i) for i in nl_solvers_set):
|
496 | 499 | if use_presolve:
|
497 | 500 | raise unittest.SkipTest(
|
498 |
| - f'cannot yet get duals if NLWriter presolve is on' |
| 501 | + 'cannot yet get duals if NLWriter presolve is on' |
499 | 502 | )
|
500 | 503 | else:
|
501 | 504 | opt.config.writer_config.linear_presolve = False
|
@@ -544,6 +547,73 @@ class TestSolvers(unittest.TestCase):
|
544 | 547 | def test_config_overwrite(self, name: str, opt_class: Type[SolverBase]):
|
545 | 548 | self.assertIsNot(SolverBase.CONFIG, opt_class.CONFIG)
|
546 | 549 |
|
| 550 | + @parameterized.expand(input=_load_tests(all_solvers)) |
| 551 | + def test_results_object_populated( |
| 552 | + self, name: str, opt_class: Type[SolverBase], use_presolve: bool |
| 553 | + ): |
| 554 | + opt: SolverBase = opt_class() |
| 555 | + if not opt.available(): |
| 556 | + raise unittest.SkipTest(f'Solver {opt.name} not available.') |
| 557 | + if any(name.startswith(i) for i in nl_solvers_set): |
| 558 | + if use_presolve: |
| 559 | + opt.config.writer_config.linear_presolve = True |
| 560 | + else: |
| 561 | + opt.config.writer_config.linear_presolve = False |
| 562 | + m = pyo.ConcreteModel() |
| 563 | + m.x = pyo.Var(bounds=(2, None)) |
| 564 | + m.obj = pyo.Objective(expr=m.x) |
| 565 | + res = opt.solve(m, load_solutions=False) |
| 566 | + pyo.assert_optimal_termination(res) |
| 567 | + |
| 568 | + # Initial gut check - is it the right type? |
| 569 | + self.assertIsInstance(res, Results) |
| 570 | + |
| 571 | + # termination_condition is set to a valid enum and not unknown |
| 572 | + self.assertIsInstance(res.termination_condition, TerminationCondition) |
| 573 | + self.assertNotEqual(res.termination_condition, TerminationCondition.unknown) |
| 574 | + |
| 575 | + # solution_status is a valid enum and indicates a usable solution |
| 576 | + self.assertIsInstance(res.solution_status, SolutionStatus) |
| 577 | + self.assertIn( |
| 578 | + res.solution_status, {SolutionStatus.feasible, SolutionStatus.optimal} |
| 579 | + ) |
| 580 | + |
| 581 | + # solver_name is a nonempty string |
| 582 | + self.assertIsInstance(res.solver_name, str) |
| 583 | + self.assertTrue(res.solver_name.strip()) |
| 584 | + |
| 585 | + # solver_version is a tuple of ints |
| 586 | + self.assertIsInstance(res.solver_version, tuple) |
| 587 | + for v in res.solver_version: |
| 588 | + self.assertIsInstance(v, int) |
| 589 | + |
| 590 | + # iteration_count is nonnegative |
| 591 | + self.assertGreaterEqual(res.iteration_count, 0) |
| 592 | + |
| 593 | + # timing_info should exist |
| 594 | + self.assertIsNotNone(res.timing_info) |
| 595 | + |
| 596 | + # start_timestamp must be a valid datetime |
| 597 | + self.assertIsInstance(res.timing_info.start_timestamp, datetime.datetime) |
| 598 | + |
| 599 | + # wall_time must be a float (=> 0) |
| 600 | + self.assertIsInstance(res.timing_info.wall_time, float) |
| 601 | + self.assertGreaterEqual(res.timing_info.wall_time, 0.0) |
| 602 | + |
| 603 | + # incumbent_objective should be populated for a feasible/optimal solve |
| 604 | + self.assertIsNotNone(res.incumbent_objective) |
| 605 | + |
| 606 | + # Should have a solution loader available |
| 607 | + self.assertTrue(hasattr(res, "solution_loader")) |
| 608 | + |
| 609 | + # Should have a copy of the config used |
| 610 | + self.assertIsInstance(res.solver_config, SolverConfig) |
| 611 | + |
| 612 | + # All solvers should be implementing some sort of TeeStream, |
| 613 | + # so they should be able to capture anything logged to the console |
| 614 | + self.assertIsNotNone(res.solver_log) |
| 615 | + self.assertIsInstance(res.solver_log, str) |
| 616 | + |
547 | 617 | @parameterized.expand(input=_load_tests(all_solvers))
|
548 | 618 | def test_remove_variable_and_objective(
|
549 | 619 | self, name: str, opt_class: Type[SolverBase], use_presolve
|
|
0 commit comments