Skip to content

Commit 7d8e631

Browse files
authored
Merge pull request #3607 from jsiirola/testing-cleanup
Minor numpy2 compatibility / testing cleanup
2 parents 18def95 + c538d53 commit 7d8e631

File tree

5 files changed

+80
-38
lines changed

5 files changed

+80
-38
lines changed

pyomo/core/base/indexed_component.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1197,7 +1197,17 @@ class IndexedComponent_NDArrayMixin(object):
11971197
11981198
"""
11991199

1200-
def __array__(self, dtype=None):
1200+
def __array__(self, dtype=None, copy=None):
1201+
if dtype not in (None, object):
1202+
raise ValueError(
1203+
"Pyomo IndexedComponents can only be converted to NumPy "
1204+
f"arrays with dtype=object (received {dtype=})"
1205+
)
1206+
if copy is not None and not copy:
1207+
raise ValueError(
1208+
"Pyomo IndexedComponents do not support conversion to NumPy "
1209+
"arrays without generating a new array"
1210+
)
12011211
if not self.is_indexed():
12021212
ans = _ndarray.NumericNDArray(shape=(1,), dtype=object)
12031213
ans[0] = self

pyomo/core/tests/examples/test_kernel_examples.py

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -14,38 +14,29 @@
1414
#
1515

1616
import glob
17+
import os
18+
import platform
19+
import subprocess
1720
import sys
18-
from os.path import basename, dirname, abspath, join
1921

20-
import subprocess
2122
import pyomo.common.unittest as unittest
23+
import pyomo.environ
2224

2325
from pyomo.common.dependencies import numpy_available, scipy_available
24-
25-
import platform
26+
from pyomo.common.fileutils import PYOMO_ROOT_DIR
27+
from pyomo.solvers.tests.solvers import test_solver_cases as _test_solver_cases
2628

2729
if platform.python_implementation() == "PyPy":
2830
# The scipy is importable into PyPy, but ODE integrators don't work. (2/ 18)
2931
scipy_available = False
3032

31-
currdir = dirname(abspath(__file__))
32-
topdir = dirname(dirname(dirname(dirname(dirname(abspath(__file__))))))
33-
examplesdir = join(topdir, "examples", "kernel")
34-
35-
examples = glob.glob(join(examplesdir, "*.py"))
36-
examples.extend(glob.glob(join(examplesdir, "mosek", "*.py")))
37-
3833
testing_solvers = {}
3934
testing_solvers['ipopt', 'nl'] = False
4035
testing_solvers['glpk', 'lp'] = False
4136
testing_solvers['mosek_direct', 'python'] = False
4237

4338

4439
def setUpModule():
45-
global testing_solvers
46-
import pyomo.environ
47-
from pyomo.solvers.tests.solvers import test_solver_cases as _test_solver_cases
48-
4940
for _solver, _io in _test_solver_cases():
5041
if (_solver, _io) in testing_solvers and _test_solver_cases(
5142
_solver, _io
@@ -60,19 +51,6 @@ def create_method(example):
6051
# this is the case since we are returning the function object
6152
# and placing it on the class with a different name.
6253
def testmethod(self):
63-
if basename(example) == "piecewise_nd_functions.py":
64-
if (
65-
(not numpy_available)
66-
or (not scipy_available)
67-
or (not testing_solvers['ipopt', 'nl'])
68-
or (not testing_solvers['glpk', 'lp'])
69-
):
70-
self.skipTest("Numpy or Scipy or Ipopt or Glpk is not available")
71-
elif "mosek" in example:
72-
if (not testing_solvers['ipopt', 'nl']) or (
73-
not testing_solvers['mosek_direct', 'python']
74-
):
75-
self.skipTest("Ipopt or Mosek is not available")
7654
result = subprocess.run(
7755
[sys.executable, example],
7856
stdout=subprocess.PIPE,
@@ -81,15 +59,35 @@ def testmethod(self):
8159
)
8260
self.assertEqual(result.returncode, 0, msg=result.stdout)
8361

84-
return testmethod
62+
tm = testmethod
63+
if os.path.basename(example) == "piecewise_nd_functions.py":
64+
tm = unittest.skipUnless(numpy_available, "Test requires numpy")(tm)
65+
tm = unittest.skipUnless(scipy_available, "Test requires scipy")(tm)
66+
tm = unittest.skipUnless(testing_solvers['ipopt', 'nl'], "Test requires ipopt")(
67+
tm
68+
)
69+
tm = unittest.skipUnless(testing_solvers['glpk', 'lp'], "Test requires glpk")(
70+
tm
71+
)
72+
elif "mosek" in example:
73+
tm = unittest.skipUnless(testing_solvers['ipopt', 'nl'], "Test requires ipopt")(
74+
tm
75+
)
76+
tm = unittest.skipUnless(
77+
testing_solvers['mosek_direct', 'python'], "Test requires mosek"
78+
)(tm)
79+
return tm
8580

8681

8782
class TestKernelExamples(unittest.TestCase):
8883
pass
8984

9085

86+
examplesdir = os.path.join(PYOMO_ROOT_DIR, "examples", "kernel")
87+
examples = glob.glob(os.path.join(examplesdir, "*.py"))
88+
examples.extend(glob.glob(os.path.join(examplesdir, "mosek", "*.py")))
9189
for filename in examples:
92-
testname = basename(filename)
90+
testname = os.path.basename(filename)
9391
assert testname.endswith(".py")
9492
testname = "test_" + testname[:-3] + "_example"
9593
setattr(TestKernelExamples, testname, create_method(filename))

pyomo/core/tests/unit/test_numpy_expr.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,37 @@ def test_indexed_constraint(self):
262262
)
263263
)
264264

265+
def test_numpy_array_copy_errors(self):
266+
# Defer testing until here to avoid the unconditional numpy dereference
267+
if int(np.__version__.split('.')[0]) < 2:
268+
self.skipTest("requires numpy>=2")
269+
270+
m = ConcreteModel()
271+
m.x = Var([0, 1, 2])
272+
with self.assertRaisesRegex(
273+
ValueError,
274+
"Pyomo IndexedComponents do not support conversion to NumPy "
275+
"arrays without generating a new array",
276+
):
277+
np.asarray(m.x, copy=False)
278+
279+
def test_numpy_array_dtype_errors(self):
280+
m = ConcreteModel()
281+
m.x = Var([0, 1, 2])
282+
# object is OK
283+
a = np.asarray(m.x, object)
284+
self.assertEqual(a.shape, (3,))
285+
# None is OK
286+
a = np.asarray(m.x, None)
287+
self.assertEqual(a.shape, (3,))
288+
# Anything else is an error
289+
with self.assertRaisesRegex(
290+
ValueError,
291+
"Pyomo IndexedComponents can only be converted to NumPy arrays "
292+
r"with dtype=object \(received dtype=.*int32",
293+
):
294+
a = np.asarray(m.x, np.int32)
295+
265296
def test_init_param_from_ndarray(self):
266297
# Test issue #2033
267298
m = ConcreteModel()

pyomo/repn/ampl.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,6 +1137,16 @@ def check_constant(self, ans, obj):
11371137
# attempt to convert the value to a float before
11381138
# proceeding.
11391139
#
1140+
# Note that as of NumPy 1.25, blindly casting a
1141+
# 1-element ndarray to a float will generate a
1142+
# deprecation warning. We will explicitly test for
1143+
# that, but want to do the test without triggering the
1144+
# numpy import
1145+
for cls in ans.__class__.__mro__:
1146+
if cls.__name__ == 'ndarray' and cls.__module__ == 'numpy':
1147+
if len(ans) == 1:
1148+
ans = ans[0]
1149+
break
11401150
# TODO: we should check bool and warn/error (while bool is
11411151
# convertible to float in Python, they have very
11421152
# different semantic meanings in Pyomo).

pyomo/solvers/tests/solvers.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -274,13 +274,6 @@ def test_solver_cases(*args):
274274
io_options={"skip_objective_sense": True},
275275
)
276276

277-
_test_solver_cases['glpk', 'python'] = initialize(
278-
name='glpk',
279-
io='python',
280-
capabilities=_glpk_capabilities,
281-
import_suffixes=[],
282-
)
283-
284277
#
285278
# CBC
286279
#

0 commit comments

Comments
 (0)