Skip to content

Commit 5b92b9c

Browse files
Merge branch 'main' into fix-pyros-intersection-set
2 parents 3fc2547 + 13facca commit 5b92b9c

File tree

22 files changed

+2190
-88
lines changed

22 files changed

+2190
-88
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/common/tests/test_unittest.py

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import datetime
1313
import multiprocessing
1414
import os
15+
import pickle
1516
import time
1617

1718
import pyomo.common.unittest as unittest
@@ -191,23 +192,24 @@ def test_timeout_timeout(self):
191192
time.sleep(1)
192193
self.assertEqual(0, 0)
193194

194-
@unittest.timeout(10)
195-
def test_timeout_skip(self):
196-
if TestPyomoUnittest.test_timeout_skip.skip:
195+
def timeout_skip(self, skip):
196+
if skip:
197197
self.skipTest("Skipping this test")
198198
self.assertEqual(0, 1)
199199

200-
test_timeout_skip.skip = True
200+
@unittest.timeout(10)
201+
def test_timeout_skip(self):
202+
self.timeout_skip(True)
201203

202-
def test_timeout_skip_fails(self):
203-
try:
204-
with self.assertRaisesRegex(unittest.SkipTest, r"Skipping this test"):
205-
self.test_timeout_skip()
206-
TestPyomoUnittest.test_timeout_skip.skip = False
207-
with self.assertRaisesRegex(AssertionError, r"0 != 1"):
208-
self.test_timeout_skip()
209-
finally:
210-
TestPyomoUnittest.test_timeout_skip.skip = True
204+
@unittest.timeout(10)
205+
def test_timeout_skip_pass(self):
206+
with self.assertRaisesRegex(AssertionError, r"0 != 1"):
207+
self.timeout_skip(False)
208+
209+
@unittest.expectedFailure
210+
@unittest.timeout(10)
211+
def test_timeout_skip_fail(self):
212+
self.timeout_skip(False)
211213

212214
@unittest.timeout(10)
213215
def bound_function(self):
@@ -218,7 +220,9 @@ def test_bound_function(self):
218220
self.bound_function()
219221
return
220222
with LoggingIntercept() as LOG:
221-
with self.assertRaises((TypeError, EOFError, AttributeError)):
223+
with self.assertRaises(
224+
(TypeError, EOFError, AttributeError, pickle.PicklingError)
225+
):
222226
self.bound_function()
223227
self.assertIn("platform that does not support 'fork'", LOG.getvalue())
224228
self.assertIn("one of its arguments is not serializable", LOG.getvalue())

pyomo/common/unittest.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -318,10 +318,28 @@ def _runner(pipe, qualname):
318318
resultType = _RunnerResult.unittest
319319

320320
def fcn():
321-
s = _unittest.TestLoader().loadTestsFromName(qualname)
322-
r = _unittest.TestResult()
323-
s.run(r)
324-
return r.errors + r.failures, r.skipped
321+
suite = _unittest.TestLoader().loadTestsFromName(qualname)
322+
result = _unittest.TestResult()
323+
# Starting in Python 3.14, the default interface is
324+
# forkserver, so timeout will fall back on spawn (getting us
325+
# here). Unfortunately, starting in pytest 9.0, if the
326+
# test is expecting to fail, pytest will completely suppress
327+
# recording the failure from the subTest, causing the main
328+
# test to unexpectedly succeed. We will resolve this by
329+
# looking at the testmethod we just loaded and if we are
330+
# expecting a failure, then we will turn that off *in the
331+
# subTest* so that the result is properly propagated.
332+
for test in suite:
333+
test.__unittest_expecting_failure__ = False
334+
# Note: fetch the unbound function off the class and not
335+
# the bound method.
336+
func = getattr(test.__class__, test._testMethodName)
337+
if hasattr(func, '__unittest_expecting_failure__'):
338+
delattr(func, '__unittest_expecting_failure__')
339+
# Now we can actually run the test (including all necessary
340+
# setUp/tearDown)
341+
suite.run(result)
342+
return result.errors + result.failures, result.skipped
325343

326344
args = ()
327345
kwargs = {}

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/observer/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# ___________________________________________________________________________
2+
#
3+
# Pyomo: Python Optimization Modeling Objects
4+
# Copyright (c) 2008-2025
5+
# National Technology and Engineering Solutions of Sandia, LLC
6+
# Under the terms of Contract DE-NA0003525 with National Technology and
7+
# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
8+
# rights in this software.
9+
# This software is distributed under the 3-clause BSD License.
10+
# ___________________________________________________________________________
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# ___________________________________________________________________________
2+
#
3+
# Pyomo: Python Optimization Modeling Objects
4+
# Copyright (c) 2008-2025
5+
# National Technology and Engineering Solutions of Sandia, LLC
6+
# Under the terms of Contract DE-NA0003525 with National Technology and
7+
# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
8+
# rights in this software.
9+
# This software is distributed under the 3-clause BSD License.
10+
# ___________________________________________________________________________
11+
12+
from pyomo.core.expr.visitor import StreamBasedExpressionVisitor
13+
from pyomo.core.expr.numeric_expr import (
14+
ExternalFunctionExpression,
15+
NegationExpression,
16+
PowExpression,
17+
MaxExpression,
18+
MinExpression,
19+
ProductExpression,
20+
MonomialTermExpression,
21+
DivisionExpression,
22+
SumExpression,
23+
Expr_ifExpression,
24+
UnaryFunctionExpression,
25+
AbsExpression,
26+
)
27+
from pyomo.core.expr.relational_expr import (
28+
RangedExpression,
29+
InequalityExpression,
30+
EqualityExpression,
31+
)
32+
from pyomo.core.base.var import VarData, ScalarVar
33+
from pyomo.core.base.param import ParamData, ScalarParam
34+
from pyomo.core.base.expression import ExpressionData, ScalarExpression
35+
from pyomo.repn.util import ExitNodeDispatcher
36+
from pyomo.common.collections import ComponentSet
37+
38+
39+
def handle_var(node, collector):
40+
collector.variables.add(node)
41+
return None
42+
43+
44+
def handle_param(node, collector):
45+
collector.params.add(node)
46+
return None
47+
48+
49+
def handle_named_expression(node, collector):
50+
collector.named_expressions.add(node)
51+
return None
52+
53+
54+
def handle_external_function(node, collector):
55+
collector.external_functions.add(node)
56+
return None
57+
58+
59+
def handle_skip(node, collector):
60+
return None
61+
62+
63+
collector_handlers = ExitNodeDispatcher()
64+
collector_handlers[VarData] = handle_var
65+
collector_handlers[ParamData] = handle_param
66+
collector_handlers[ExpressionData] = handle_named_expression
67+
collector_handlers[ScalarExpression] = handle_named_expression
68+
collector_handlers[ExternalFunctionExpression] = handle_external_function
69+
collector_handlers[NegationExpression] = handle_skip
70+
collector_handlers[PowExpression] = handle_skip
71+
collector_handlers[MaxExpression] = handle_skip
72+
collector_handlers[MinExpression] = handle_skip
73+
collector_handlers[ProductExpression] = handle_skip
74+
collector_handlers[MonomialTermExpression] = handle_skip
75+
collector_handlers[DivisionExpression] = handle_skip
76+
collector_handlers[SumExpression] = handle_skip
77+
collector_handlers[Expr_ifExpression] = handle_skip
78+
collector_handlers[UnaryFunctionExpression] = handle_skip
79+
collector_handlers[AbsExpression] = handle_skip
80+
collector_handlers[RangedExpression] = handle_skip
81+
collector_handlers[InequalityExpression] = handle_skip
82+
collector_handlers[EqualityExpression] = handle_skip
83+
collector_handlers[int] = handle_skip
84+
collector_handlers[float] = handle_skip
85+
86+
87+
class _ComponentFromExprCollector(StreamBasedExpressionVisitor):
88+
def __init__(self, **kwds):
89+
self.named_expressions = ComponentSet()
90+
self.variables = ComponentSet()
91+
self.params = ComponentSet()
92+
self.external_functions = ComponentSet()
93+
super().__init__(**kwds)
94+
95+
def exitNode(self, node, data):
96+
return collector_handlers[node.__class__](node, self)
97+
98+
def beforeChild(self, node, child, child_idx):
99+
if child in self.named_expressions:
100+
return False, None
101+
return True, None
102+
103+
104+
_visitor = _ComponentFromExprCollector()
105+
106+
107+
def collect_components_from_expr(expr):
108+
_visitor.__init__()
109+
_visitor.walk_expression(expr)
110+
return (
111+
_visitor.named_expressions,
112+
_visitor.variables,
113+
_visitor.params,
114+
_visitor.external_functions,
115+
)

0 commit comments

Comments
 (0)