Skip to content

Commit 8edfb8f

Browse files
committed
Merge remote-tracking branch 'origin/main' into observer
2 parents cfdf7d6 + 8397f66 commit 8edfb8f

File tree

12 files changed

+62
-29
lines changed

12 files changed

+62
-29
lines changed

examples/pyomo/piecewise/indexed.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
# This software is distributed under the 3-clause BSD License.
1010
# ___________________________________________________________________________
1111

12-
# A piewise approximiation of a nonconvex objective function.
12+
# A piecewise approximation of a nonconvex objective function.
1313

1414
import pyomo.environ as pyo
1515

pyomo/contrib/doe/tests/test_doe_solve.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,9 @@ def test_reactor_obj_cholesky_solve(self):
251251
# Make sure FIM and Q.T @ sigma_inv @ Q are close (alternate definition of FIM)
252252
self.assertTrue(np.all(np.isclose(FIM, Q.T @ sigma_inv @ Q)))
253253

254-
def test_reactor_obj_cholesky_solve_bad_prior(self):
254+
def DISABLE_test_reactor_obj_cholesky_solve_bad_prior(self):
255+
# [10/2025] This test has been disabled because it frequently
256+
# (and randomly) returns "infeasible" when run on Windows.
255257
from pyomo.contrib.doe.doe import _SMALL_TOLERANCE_DEFINITENESS
256258

257259
fd_method = "central"

pyomo/contrib/solver/common/results.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -197,14 +197,6 @@ def __init__(
197197
description="A tuple representing the version of the solver in use.",
198198
),
199199
)
200-
self.iteration_count: Optional[int] = self.declare(
201-
'iteration_count',
202-
ConfigValue(
203-
domain=NonNegativeInt,
204-
default=None,
205-
description="The total number of iterations.",
206-
),
207-
)
208200
self.timing_info: ConfigDict = self.declare(
209201
'timing_info', ConfigDict(implicit=True)
210202
)

pyomo/contrib/solver/solvers/gurobi_direct.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import math
1515
import operator
1616
import os
17+
import logging
1718

1819
from pyomo.common.collections import ComponentMap, ComponentSet
1920
from pyomo.common.config import ConfigValue
@@ -43,6 +44,7 @@
4344
)
4445
from pyomo.contrib.solver.common.solution_loader import SolutionLoaderBase
4546

47+
logger = logging.getLogger(__name__)
4648

4749
gurobipy, gurobipy_available = attempt_import('gurobipy')
4850

@@ -442,7 +444,9 @@ def _postsolve(self, timer: HierarchicalTimer, config, loader):
442444
results.incumbent_objective = None
443445
results.objective_bound = None
444446

445-
results.iteration_count = grb_model.getAttr('IterCount')
447+
results.extra_info.IterCount = grb_model.getAttr('IterCount')
448+
results.extra_info.BarIterCount = grb_model.getAttr('BarIterCount')
449+
results.extra_info.NodeCount = grb_model.getAttr('NodeCount')
446450

447451
timer.start('load solution')
448452
if config.load_solutions:

pyomo/contrib/solver/solvers/gurobi_persistent.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -857,7 +857,9 @@ def _postsolve(self, timer: HierarchicalTimer):
857857
):
858858
results.incumbent_objective = None
859859

860-
results.iteration_count = gprob.getAttr('IterCount')
860+
results.extra_info.IterCount = gprob.getAttr('IterCount')
861+
results.extra_info.BarIterCount = gprob.getAttr('BarIterCount')
862+
results.extra_info.NodeCount = gprob.getAttr('NodeCount')
861863

862864
timer.start('load solution')
863865
if config.load_solutions:

pyomo/contrib/solver/solvers/highs.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -750,7 +750,15 @@ def _postsolve(self, stream: io.StringIO):
750750
results.objective_bound = None
751751
else:
752752
results.objective_bound = info.mip_dual_bound
753-
results.iteration_count = info.simplex_iteration_count
753+
754+
if info.valid:
755+
results.extra_info.simplex_iteration_count = (
756+
info.simplex_iteration_count
757+
)
758+
results.extra_info.ipm_iteration_count = info.ipm_iteration_count
759+
results.extra_info.mip_node_count = info.mip_node_count
760+
results.extra_info.pdlp_iteration_count = info.pdlp_iteration_count
761+
results.extra_info.qp_iteration_count = info.qp_iteration_count
754762

755763
if config.load_solutions:
756764
if has_feasible_solution:

pyomo/contrib/solver/solvers/ipopt.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ def solve(self, model, **kwds) -> Results:
473473
results = Results()
474474
results.termination_condition = TerminationCondition.provenInfeasible
475475
results.solution_loader = SolSolutionLoader(None, None)
476-
results.iteration_count = 0
476+
results.extra_info.iteration_count = 0
477477
results.timing_info.total_seconds = 0
478478
elif len(nl_info.variables) == 0:
479479
if len(nl_info.eliminated_vars) == 0:
@@ -487,7 +487,7 @@ def solve(self, model, **kwds) -> Results:
487487
)
488488
results.solution_status = SolutionStatus.optimal
489489
results.solution_loader = SolSolutionLoader(None, nl_info=nl_info)
490-
results.iteration_count = 0
490+
results.extra_info.iteration_count = 0
491491
results.timing_info.total_seconds = 0
492492
else:
493493
if os.path.isfile(basename + '.sol'):
@@ -503,7 +503,9 @@ def solve(self, model, **kwds) -> Results:
503503
results.solution_loader = SolSolutionLoader(None, None)
504504
else:
505505
try:
506-
results.iteration_count = parsed_output_data.pop('iters')
506+
results.extra_info.iteration_count = parsed_output_data.pop(
507+
'iters'
508+
)
507509
cpu_seconds = parsed_output_data.pop('cpu_seconds')
508510
for k, v in cpu_seconds.items():
509511
results.timing_info[k] = v

pyomo/contrib/solver/solvers/knitro/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def solve(self, model: BlockData, **kwds) -> Results:
8282
if config.restore_variable_values_after_solve:
8383
self._save_var_values()
8484

85-
with capture_output(TeeStream(self._stream, *config.tee), capture_fd=False):
85+
with capture_output(TeeStream(self._stream, *config.tee), capture_fd=True):
8686
self._solve(config, timer)
8787

8888
if config.restore_variable_values_after_solve:
@@ -141,7 +141,7 @@ def _postsolve(self, config: KnitroConfig, timer: HierarchicalTimer) -> Results:
141141
results.solution_status = self._get_solution_status(status)
142142
results.termination_condition = self._get_termination_condition(status)
143143
results.incumbent_objective = self._engine.get_obj_value()
144-
results.iteration_count = self._engine.get_num_iters()
144+
results.extra_info.iteration_count = self._engine.get_num_iters()
145145
results.timing_info.solve_time = self._engine.get_solve_time()
146146
results.timing_info.timer = timer
147147

pyomo/contrib/solver/tests/solvers/test_ipopt.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -594,13 +594,13 @@ def test_ipopt_quiet_print_level(self):
594594
result = ipopt.Ipopt().solve(model, solver_options={'print_level': 0})
595595
# IPOPT doesn't tell us anything about the iters if the print level
596596
# is set to 0
597-
self.assertIsNone(result.iteration_count)
597+
self.assertFalse(hasattr(result.extra_info, 'iteration_count'))
598598
self.assertFalse(hasattr(result.extra_info, 'iteration_log'))
599599
model = self.create_model()
600600
result = ipopt.Ipopt().solve(model, solver_options={'print_level': 3})
601601
# At a slightly higher level, we get some of the info, like
602602
# iteration count, but NOT iteration_log
603-
self.assertEqual(result.iteration_count, 11)
603+
self.assertEqual(result.extra_info.iteration_count, 11)
604604
self.assertFalse(hasattr(result.extra_info, 'iteration_log'))
605605

606606
def test_ipopt_loud_print_level(self):
@@ -609,13 +609,13 @@ def test_ipopt_loud_print_level(self):
609609
result = ipopt.Ipopt().solve(model, solver_options={'print_level': 8})
610610
# Nothing unexpected should be in the results object at this point,
611611
# except that the solver_log is significantly longer
612-
self.assertEqual(result.iteration_count, 11)
612+
self.assertEqual(result.extra_info.iteration_count, 11)
613613
self.assertEqual(result.incumbent_objective, 7.013645951336496e-25)
614614
self.assertIn('Optimal Solution Found', result.extra_info.solver_message)
615615
self.assertTrue(hasattr(result.extra_info, 'iteration_log'))
616616
model = self.create_model()
617617
result = ipopt.Ipopt().solve(model, solver_options={'print_level': 12})
618-
self.assertEqual(result.iteration_count, 11)
618+
self.assertEqual(result.extra_info.iteration_count, 11)
619619
self.assertEqual(result.incumbent_objective, 7.013645951336496e-25)
620620
self.assertIn('Optimal Solution Found', result.extra_info.solver_message)
621621
self.assertTrue(hasattr(result.extra_info, 'iteration_log'))
@@ -624,7 +624,7 @@ def test_ipopt_results(self):
624624
model = self.create_model()
625625
results = ipopt.Ipopt().solve(model)
626626
self.assertEqual(results.solver_name, 'ipopt')
627-
self.assertEqual(results.iteration_count, 11)
627+
self.assertEqual(results.extra_info.iteration_count, 11)
628628
self.assertEqual(results.incumbent_objective, 7.013645951336496e-25)
629629
self.assertIn('Optimal Solution Found', results.extra_info.solver_message)
630630

pyomo/contrib/solver/tests/solvers/test_knitro_direct.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
# This software is distributed under the 3-clause BSD License.
1010
# ___________________________________________________________________________
1111

12+
import io
13+
from contextlib import redirect_stdout
14+
1215
import pyomo.common.unittest as unittest
1316

1417
from pyomo.contrib.solver.solvers.knitro.config import KnitroConfig
@@ -87,6 +90,32 @@ class TestKnitroDirectSolver(unittest.TestCase):
8790
def setUp(self):
8891
self.opt = KnitroDirectSolver()
8992

93+
def test_solve_tee(self):
94+
m = pyo.ConcreteModel()
95+
m.x = pyo.Var(initialize=1.5, bounds=(-5, 5))
96+
m.y = pyo.Var(initialize=1.5, bounds=(-5, 5))
97+
m.obj = pyo.Objective(
98+
expr=(1.0 - m.x) + 100.0 * (m.y - m.x), sense=pyo.minimize
99+
)
100+
stream = io.StringIO()
101+
with redirect_stdout(stream):
102+
self.opt.solve(m, tee=True)
103+
output = stream.getvalue()
104+
self.assertTrue(bool(output.strip()))
105+
106+
def test_solve_no_tee(self):
107+
m = pyo.ConcreteModel()
108+
m.x = pyo.Var(initialize=1.5, bounds=(-5, 5))
109+
m.y = pyo.Var(initialize=1.5, bounds=(-5, 5))
110+
m.obj = pyo.Objective(
111+
expr=(1.0 - m.x) + 100.0 * (m.y - m.x), sense=pyo.minimize
112+
)
113+
stream = io.StringIO()
114+
with redirect_stdout(stream):
115+
self.opt.solve(m, tee=False)
116+
output = stream.getvalue()
117+
self.assertFalse(bool(output.strip()))
118+
90119
def test_solve(self):
91120
m = pyo.ConcreteModel()
92121
m.x = pyo.Var(initialize=1.5, bounds=(-5, 5))

0 commit comments

Comments
 (0)