From d909d6f58ed5d2a566f9e1dea7d3d56dbb40ff1a Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 6 Nov 2024 22:25:26 -0700 Subject: [PATCH 01/21] testing: add missing guards for networkx_available --- .../interfaces/tests/test_external_pyomo_block.py | 14 ++++++++++++-- .../interfaces/tests/test_external_pyomo_model.py | 4 ++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/pynumero/interfaces/tests/test_external_pyomo_block.py b/pyomo/contrib/pynumero/interfaces/tests/test_external_pyomo_block.py index 913d3055c9c..1807f24afdd 100644 --- a/pyomo/contrib/pynumero/interfaces/tests/test_external_pyomo_block.py +++ b/pyomo/contrib/pynumero/interfaces/tests/test_external_pyomo_block.py @@ -15,10 +15,10 @@ from pyomo.core.expr.visitor import identify_variables import pyomo.environ as pyo +from pyomo.common.dependencies import networkx_available as nx_available from pyomo.contrib.pynumero.dependencies import ( numpy as np, numpy_available, - scipy, scipy_available, ) @@ -151,6 +151,7 @@ def flow_out_eqn(m, t): class TestExternalGreyBoxBlock(unittest.TestCase): + @unittest.skipUnless(nx_available, "SCCImplicitFunctionSolver requires networkx") def test_construct_scalar(self): m = pyo.ConcreteModel() m.ex_block = ExternalGreyBoxBlock(concrete=True) @@ -171,6 +172,7 @@ def test_construct_scalar(self): self.assertEqual(len(block.outputs), 0) self.assertEqual(len(block._equality_constraint_names), 2) + @unittest.skipUnless(nx_available, "SCCImplicitFunctionSolver requires networkx") def test_construct_indexed(self): block = ExternalGreyBoxBlock([0, 1, 2], concrete=True) self.assertIs(type(block), IndexedExternalGreyBoxBlock) @@ -192,6 +194,7 @@ def test_construct_indexed(self): self.assertEqual(len(b._equality_constraint_names), 2) @unittest.skipUnless(cyipopt_available, "cyipopt is not available") + @unittest.skipUnless(nx_available, "SCCImplicitFunctionSolver requires networkx") def test_solve_square(self): m = pyo.ConcreteModel() m.ex_block = ExternalGreyBoxBlock(concrete=True) @@ -234,6 +237,7 @@ def test_solve_square(self): self.assertAlmostEqual(m_ex.y.value, y.value, delta=1e-8) @unittest.skipUnless(cyipopt_available, "cyipopt is not available") + @unittest.skipUnless(nx_available, "SCCImplicitFunctionSolver requires networkx") def test_optimize(self): m = pyo.ConcreteModel() m.ex_block = ExternalGreyBoxBlock(concrete=True) @@ -292,6 +296,7 @@ def test_optimize(self): self.assertAlmostEqual(m_ex.y.value, y.value, delta=1e-8) @unittest.skipUnless(cyipopt_available, "cyipopt is not available") + @unittest.skipUnless(nx_available, "SCCImplicitFunctionSolver requires networkx") def test_optimize_with_cyipopt_for_inner_problem(self): # Use CyIpopt, rather than the default SciPy solvers, # for the inner problem @@ -427,6 +432,7 @@ def test_optimize_no_decomposition(self): self.assertAlmostEqual(m_ex.x.value, x.value, delta=1e-8) self.assertAlmostEqual(m_ex.y.value, y.value, delta=1e-8) + @unittest.skipUnless(nx_available, "SCCImplicitFunctionSolver requires networkx") def test_construct_dynamic(self): m = make_dynamic_model() time = m.time @@ -504,6 +510,7 @@ def test_construct_dynamic(self): ) @unittest.skipUnless(cyipopt_available, "cyipopt is not available") + @unittest.skipUnless(nx_available, "SCCImplicitFunctionSolver requires networkx") def test_solve_square_dynamic(self): # Create the "external model" m = make_dynamic_model() @@ -571,6 +578,7 @@ def linking_constraint_rule(m, i, t): self.assertStructuredAlmostEqual(values, target_values, delta=1e-5) @unittest.skipUnless(cyipopt_available, "cyipopt is not available") + @unittest.skipUnless(nx_available, "SCCImplicitFunctionSolver requires networkx") def test_optimize_dynamic(self): # Create the "external model" m = make_dynamic_model() @@ -653,6 +661,7 @@ def linking_constraint_rule(m, i, t): self.assertStructuredAlmostEqual(values, target_values, delta=1e-5) @unittest.skipUnless(cyipopt_available, "cyipopt is not available") + @unittest.skipUnless(nx_available, "SCCImplicitFunctionSolver requires networkx") def test_optimize_dynamic_references(self): """ When when pre-existing variables are attached to the EGBB @@ -717,7 +726,8 @@ def test_optimize_dynamic_references(self): self.assertStructuredAlmostEqual(values, target_values, delta=1e-5) -class TestPyomoNLPWithGreyBoxBLocks(unittest.TestCase): +@unittest.skipUnless(nx_available, "SCCImplicitFunctionSolver requires networkx") +class TestPyomoNLPWithGreyBoxBlocks(unittest.TestCase): def test_set_and_evaluate(self): m = pyo.ConcreteModel() m.ex_block = ExternalGreyBoxBlock(concrete=True) diff --git a/pyomo/contrib/pynumero/interfaces/tests/test_external_pyomo_model.py b/pyomo/contrib/pynumero/interfaces/tests/test_external_pyomo_model.py index 9773fa7e4a8..5e7ea023166 100644 --- a/pyomo/contrib/pynumero/interfaces/tests/test_external_pyomo_model.py +++ b/pyomo/contrib/pynumero/interfaces/tests/test_external_pyomo_model.py @@ -13,6 +13,7 @@ import pyomo.common.unittest as unittest import pyomo.environ as pyo +from pyomo.common.dependencies import networkx_available as nx_available from pyomo.contrib.pynumero.dependencies import ( numpy as np, numpy_available, @@ -513,6 +514,7 @@ def test_explicit_zeros(self): np.testing.assert_allclose(hess.data, data, rtol=1e-8) +@unittest.skipUnless(nx_available, "SCCImplicitFunctionSolver requires networkx") class TestExternalPyomoModel(unittest.TestCase): def test_evaluate_SimpleModel1(self): model = SimpleModel1() @@ -838,6 +840,7 @@ def test_evaluate_hessian_lagrangian_SimpleModel2x2_1(self): np.testing.assert_allclose(hess_lag, expected_hess_lag, rtol=1e-8) +@unittest.skipUnless(nx_available, "SCCImplicitFunctionSolver requires networkx") class TestUpdatedHessianCalculationMethods(unittest.TestCase): """ These tests exercise the methods for fast Hessian-of-Lagrangian @@ -1021,6 +1024,7 @@ def test_evaluate_hessian_equality_constraints_order(self): ) +@unittest.skipUnless(nx_available, "SCCImplicitFunctionSolver requires networkx") class TestScaling(unittest.TestCase): def con_3_body(self, x, y, u, v): return 1e5 * x**2 + 1e4 * y**2 + 1e1 * u**2 + 1e0 * v**2 From 75217188139e22809be1c356bd8bf36e8663fcbe Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 6 Nov 2024 22:29:25 -0700 Subject: [PATCH 02/21] Standardize lists of unary and binary vector ufuncs (and convert to sets) --- pyomo/contrib/pynumero/sparse/base_block.py | 127 +++++++++++++++ pyomo/contrib/pynumero/sparse/block_vector.py | 75 +-------- .../pynumero/sparse/mpi_block_vector.py | 79 +--------- .../sparse/tests/test_block_vector.py | 147 +++++++----------- 4 files changed, 196 insertions(+), 232 deletions(-) diff --git a/pyomo/contrib/pynumero/sparse/base_block.py b/pyomo/contrib/pynumero/sparse/base_block.py index 0b923ce6efb..0cbeaabfacc 100644 --- a/pyomo/contrib/pynumero/sparse/base_block.py +++ b/pyomo/contrib/pynumero/sparse/base_block.py @@ -11,6 +11,8 @@ # These classes are for checking types consistently and raising errors +from ..dependencies import numpy as np + class BaseBlockVector(object): """Base class for block vectors""" @@ -177,3 +179,128 @@ def transpose(self, *axes): def tostring(self, order='C'): msg = "tostring not implemented for {}".format(self.__class__.__name__) raise NotImplementedError(msg) + + +#: NumPy ufuncs that take one vector and are compatible with pyNumero vectors +vec_unary_ufuncs = { + ## MATH ufuncs + np.negative, + np.positive, + np.absolute, + np.fabs, + np.rint, + np.sign, + np.conj, + np.conjugate, + np.exp, + np.exp2, + np.log, + np.log2, + np.log10, + np.expm1, + np.log1p, + np.sqrt, + np.square, + np.cbrt, + np.reciprocal, + ## TRIG ufuncs + np.sin, + np.cos, + np.tan, + np.arcsin, + np.arccos, + np.arctan, + np.sinh, + np.cosh, + np.tanh, + np.arcsinh, + np.arccosh, + np.arctanh, + np.degrees, + np.radians, + np.deg2rad, + np.rad2deg, + ## COMPARISON ufuncs + np.logical_not, + ## BIT-TWIDDLING ufuncs + np.invert, + ## FLOATING ufuncs + np.isfinite, + np.isinf, + np.isnan, + # np.isnat, # only defined for datetime + np.fabs, # numpy docs list here and in MATH + np.signbit, + np.spacing, + # np.modf, # disabled because shape is not preserved + # np.frexp, # disabled because shape is not preserved + np.floor, + np.ceil, + np.trunc, + # OTHER (not listed in ufuncs docs) + np.abs, +} + +#: NumPy ufuncs that take two vectors and are compatible with pyNumero vectors +vec_binary_ufuncs = { + ## MATH ufuncs + np.add, + np.subtract, + np.multiply, + # np.matmult, # disabled because shape is not preserved + np.divide, + np.logaddexp, + np.logaddexp2, + np.true_divide, + np.floor_divide, + np.power, + np.float_power, + np.remainder, + np.mod, + np.fmod, + # np.divmod, # dieabled because shape is not preserved + np.heaviside, + np.gcd, + np.lcm, + ## TRIG ufuncs + np.arctan2, + np.hypot, + ## BIT-TWIDDLING ufuncs + np.bitwise_and, + np.bitwise_or, + np.bitwise_xor, + np.left_shift, + np.right_shift, + ## COMPARISON ufuncs + np.greater, + np.greater_equal, + np.less, + np.less_equal, + np.not_equal, + np.equal, + np.logical_and, + np.logical_or, + np.logical_xor, + np.maximum, + np.minimum, + np.fmax, + np.fmin, + ## FLOATING ufincs + np.copysign, + np.nextafter, + np.ldexp, + np.fmod, # numpy docs list here and in MATH +} + +#: NumPy ufuncs can be used as reductions for pyNumero vectors +vec_associative_reductions = { + np.add, + np.multiply, + np.bitwise_and, + np.bitwise_or, + np.bitwise_xor, + np.maximum, + np.minimum, + np.fmax, + np.fmin, +} diff --git a/pyomo/contrib/pynumero/sparse/block_vector.py b/pyomo/contrib/pynumero/sparse/block_vector.py index 37c2d7b2cf7..e722760989a 100644 --- a/pyomo/contrib/pynumero/sparse/block_vector.py +++ b/pyomo/contrib/pynumero/sparse/block_vector.py @@ -183,7 +183,12 @@ import operator from ..dependencies import numpy as np -from .base_block import BaseBlockVector +from .base_block import ( + BaseBlockVector, + vec_unary_ufuncs, + vec_binary_ufuncs, + vec_associative_reductions, +) class NotFullyDefinedBlockVectorError(Exception): @@ -265,74 +270,6 @@ def __array_wrap__(self, out_arr, context=None): return super(BlockVector, self).__array_wrap__(self, out_arr, context) def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): - """Runs ufuncs speciallizations to BlockVector""" - # functions that take one vector - unary_funcs = [ - np.log10, - np.sin, - np.cos, - np.exp, - np.ceil, - np.floor, - np.tan, - np.arctan, - np.arcsin, - np.arccos, - np.sinh, - np.cosh, - np.abs, - np.tanh, - np.arccosh, - np.arcsinh, - np.arctanh, - np.fabs, - np.sqrt, - np.log, - np.log2, - np.absolute, - np.isfinite, - np.isinf, - np.isnan, - np.log1p, - np.logical_not, - np.expm1, - np.exp2, - np.sign, - np.rint, - np.square, - np.positive, - np.negative, - np.rad2deg, - np.deg2rad, - np.conjugate, - np.reciprocal, - np.signbit, - ] - - # functions that take two vectors - binary_funcs = [ - np.add, - np.multiply, - np.divide, - np.subtract, - np.greater, - np.greater_equal, - np.less, - np.less_equal, - np.not_equal, - np.maximum, - np.minimum, - np.fmax, - np.fmin, - np.equal, - np.logical_and, - np.logical_or, - np.logical_xor, - np.logaddexp, - np.logaddexp2, - np.remainder, - np.heaviside, - np.hypot, ] args = [input_ for i, input_ in enumerate(inputs)] diff --git a/pyomo/contrib/pynumero/sparse/mpi_block_vector.py b/pyomo/contrib/pynumero/sparse/mpi_block_vector.py index f86d450a73e..f976ee2853d 100644 --- a/pyomo/contrib/pynumero/sparse/mpi_block_vector.py +++ b/pyomo/contrib/pynumero/sparse/mpi_block_vector.py @@ -11,7 +11,12 @@ from pyomo.common.dependencies import mpi4py from pyomo.contrib.pynumero.sparse import BlockVector -from .base_block import BaseBlockVector +from .base_block import ( + BaseBlockVector, + vec_unary_ufuncs, + vec_binary_ufuncs, + vec_associative_reductions, +) from .block_vector import NotFullyDefinedBlockVectorError from .block_vector import assert_block_structure as block_vector_assert_block_structure import numpy as np @@ -136,74 +141,6 @@ def __array_wrap__(self, out_arr, context=None): def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): """Runs ufuncs speciallizations to MPIBlockVector""" - # functions that take one vector - unary_funcs = [ - np.log10, - np.sin, - np.cos, - np.exp, - np.ceil, - np.floor, - np.tan, - np.arctan, - np.arcsin, - np.arccos, - np.sinh, - np.cosh, - np.abs, - np.tanh, - np.arccosh, - np.arcsinh, - np.arctanh, - np.fabs, - np.sqrt, - np.log, - np.log2, - np.absolute, - np.isfinite, - np.isinf, - np.isnan, - np.log1p, - np.logical_not, - np.expm1, - np.exp2, - np.sign, - np.rint, - np.square, - np.positive, - np.negative, - np.rad2deg, - np.deg2rad, - np.conjugate, - np.reciprocal, - np.signbit, - ] - # functions that take two vectors - binary_funcs = [ - np.add, - np.multiply, - np.divide, - np.subtract, - np.greater, - np.greater_equal, - np.less, - np.less_equal, - np.not_equal, - np.maximum, - np.minimum, - np.fmax, - np.fmin, - np.equal, - np.logical_and, - np.logical_or, - np.logical_xor, - np.logaddexp, - np.logaddexp2, - np.remainder, - np.heaviside, - np.hypot, - ] - outputs = kwargs.pop('out', None) if outputs is not None: raise NotImplementedError( @@ -211,10 +148,10 @@ def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): + ' cannot be used with MPIBlockVector if the out keyword argument is given.' ) - if ufunc in unary_funcs: + if ufunc in vec_unary_ufuncs: results = self._unary_operation(ufunc, method, *inputs, **kwargs) return results - elif ufunc in binary_funcs: + elif ufunc in vec_binary_ufuncs: results = self._binary_operation(ufunc, method, *inputs, **kwargs) return results else: diff --git a/pyomo/contrib/pynumero/sparse/tests/test_block_vector.py b/pyomo/contrib/pynumero/sparse/tests/test_block_vector.py index 610d41a09a4..5df43379f3e 100644 --- a/pyomo/contrib/pynumero/sparse/tests/test_block_vector.py +++ b/pyomo/contrib/pynumero/sparse/tests/test_block_vector.py @@ -24,6 +24,9 @@ from pyomo.contrib.pynumero.sparse.block_vector import ( BlockVector, NotFullyDefinedBlockVectorError, + vec_associative_reductions, + vec_unary_ufuncs, + vec_binary_ufuncs, ) @@ -1023,64 +1026,34 @@ def test_copy_structure(self): self.assertEqual(v.get_block(1).size, v2.get_block(1).size) def test_unary_ufuncs(self): - v = BlockVector(2) a = np.ones(3) * 0.5 b = np.ones(2) * 0.8 + v = BlockVector(2) v.set_block(0, a) v.set_block(1, b) + # Some operations only accept integers + ai = np.ones(3, dtype='i') * 5 + bi = np.ones(2, dtype='i') * 8 + vi = BlockVector(2) + vi.set_block(0, ai) + vi.set_block(1, bi) - v2 = BlockVector(2) + _int_ufuncs = {np.invert, np.arccosh} - unary_funcs = [ - np.log10, - np.sin, - np.cos, - np.exp, - np.ceil, - np.floor, - np.tan, - np.arctan, - np.arcsin, - np.arccos, - np.sinh, - np.cosh, - np.abs, - np.tanh, - np.arcsinh, - np.arctanh, - np.fabs, - np.sqrt, - np.log, - np.log2, - np.absolute, - np.isfinite, - np.isinf, - np.isnan, - np.log1p, - np.logical_not, - np.exp2, - np.expm1, - np.sign, - np.rint, - np.square, - np.positive, - np.negative, - np.rad2deg, - np.deg2rad, - np.conjugate, - np.reciprocal, - ] - - for fun in unary_funcs: - v2.set_block(0, fun(v.get_block(0))) - v2.set_block(1, fun(v.get_block(1))) - res = fun(v) + v2 = BlockVector(2) + for fun in vec_unary_ufuncs: + _v = vi if fun in _int_ufuncs else v + v2.set_block(0, fun(_v.get_block(0))) + v2.set_block(1, fun(_v.get_block(1))) + res = fun(_v) self.assertIsInstance(res, BlockVector) self.assertEqual(res.nblocks, 2) for i in range(2): self.assertTrue(np.allclose(res.get_block(i), v2.get_block(i))) - other_funcs = [np.cumsum, np.cumprod, np.cumproduct] + other_funcs = [np.cumsum, np.cumprod] + if np.__version__[0] == '1': + other_funcs.append(np.cumproduct) for fun in other_funcs: res = fun(v) @@ -1088,13 +1061,14 @@ def test_unary_ufuncs(self): self.assertEqual(res.nblocks, 2) self.assertTrue(np.allclose(fun(v.flatten()), res.flatten())) - with self.assertRaises(Exception) as context: - np.cbrt(v) + with self.assertRaises(TypeError): + np.modf(v) def test_reduce_ufuncs(self): v = BlockVector(2) - a = np.ones(3) * 0.5 - b = np.ones(2) * 0.8 + # Some operations only accept integers, so we will test with integers + a = np.ones(3, dtype='i') * 5 + b = np.ones(2, dtype='i') * 8 v.set_block(0, a) v.set_block(1, b) @@ -1108,56 +1082,45 @@ def test_reduce_ufuncs(self): def test_binary_ufuncs(self): v = BlockVector(2) - a = np.ones(3) * 0.5 - b = np.ones(2) * 0.8 - v.set_block(0, a) - v.set_block(1, b) - + v.set_blocks([np.ones(3) * 0.5, np.ones(2) * 0.8]) v2 = BlockVector(2) - a2 = np.ones(3) * 3.0 - b2 = np.ones(2) * 2.8 - v2.set_block(0, a2) - v2.set_block(1, b2) - - binary_ufuncs = [ - np.add, - np.multiply, - np.divide, - np.subtract, - np.greater, - np.greater_equal, - np.less, - np.less_equal, - np.not_equal, - np.maximum, - np.minimum, - np.fmax, - np.fmin, - np.equal, - np.logaddexp, - np.logaddexp2, - np.remainder, - np.heaviside, - np.hypot, - ] - - for fun in binary_ufuncs: - flat_res = fun(v.flatten(), v2.flatten()) - res = fun(v, v2) + v2.set_blocks([np.ones(3) * 3.0, np.ones(2) * 2.8]) + + vi = BlockVector(2) + vi.set_blocks([np.ones(3, dtype='i') * 5, np.ones(2, dtype='i') * 8]) + v2i = BlockVector(2) + v2i.set_blocks([np.ones(3, dtype='i') * 3, np.ones(2, dtype='i') * 2]) + + _int_ufuncs = { + np.gcd, + np.lcm, + np.ldexp, + np.left_shift, + np.right_shift, + np.bitwise_and, + np.bitwise_or, + np.bitwise_xor, + } + + for fun in vec_binary_ufuncs: + _v, _v2, _s = (vi, v2i, 3) if fun in _int_ufuncs else (v, v2, 3.0) + + flat_res = fun(_v.flatten(), _v2.flatten()) + res = fun(_v, _v2) self.assertTrue(np.allclose(flat_res, res.flatten())) - res = fun(v, v2.flatten()) + res = fun(_v, _v2.flatten()) self.assertTrue(np.allclose(flat_res, res.flatten())) - res = fun(v.flatten(), v2) + res = fun(_v.flatten(), _v2) self.assertTrue(np.allclose(flat_res, res.flatten())) - flat_res = fun(v.flatten(), 5) - res = fun(v, 5) + flat_res = fun(_v.flatten(), 5) + res = fun(_v, 5) self.assertTrue(np.allclose(flat_res, res.flatten())) - flat_res = fun(3.0, v2.flatten()) - res = fun(3.0, v2) + flat_res = fun(_s, _v2.flatten()) + res = fun(_s, _v2) self.assertTrue(np.allclose(flat_res, res.flatten())) v = BlockVector(2) From f3faa5515624d47ea4d2c13a7f06e661e4f13d40 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 6 Nov 2024 22:30:31 -0700 Subject: [PATCH 03/21] ndarray.ptp only exists for NumPy<2 --- pyomo/contrib/pynumero/sparse/block_vector.py | 18 ++++++++++-------- .../pynumero/sparse/tests/test_block_vector.py | 1 + 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/pyomo/contrib/pynumero/sparse/block_vector.py b/pyomo/contrib/pynumero/sparse/block_vector.py index e722760989a..531dbaed383 100644 --- a/pyomo/contrib/pynumero/sparse/block_vector.py +++ b/pyomo/contrib/pynumero/sparse/block_vector.py @@ -57,7 +57,7 @@ * :py:meth:`~numpy.ndarray.conj` * :py:meth:`~numpy.ndarray.conjugate` * :py:meth:`~numpy.ndarray.nonzero` - * :py:meth:`~numpy.ndarray.ptp` + * :py:meth:`~numpy.ndarray.ptp` (NumPy 1.x only) * :py:meth:`~numpy.ndarray.round` * :py:meth:`~numpy.ndarray.std` * :py:meth:`~numpy.ndarray.var` @@ -679,13 +679,15 @@ def nonzero(self): result.set_block(idx, self.get_block(idx).nonzero()[0]) return (result,) - def ptp(self, axis=None, out=None, keepdims=False): - """ - Peak to peak (maximum - minimum) value along a given axis. - """ - assert_block_structure(self) - assert out is None, 'Out keyword not supported' - return self.max() - self.min() + if np.__version__[0] < '2': + + def ptp(self, axis=None, out=None, keepdims=False): + """ + Peak to peak (maximum - minimum) value along a given axis. + """ + assert_block_structure(self) + assert out is None, 'Out keyword not supported' + return self.max() - self.min() def round(self, decimals=0, out=None): """ diff --git a/pyomo/contrib/pynumero/sparse/tests/test_block_vector.py b/pyomo/contrib/pynumero/sparse/tests/test_block_vector.py index 5df43379f3e..78d69bc91cd 100644 --- a/pyomo/contrib/pynumero/sparse/tests/test_block_vector.py +++ b/pyomo/contrib/pynumero/sparse/tests/test_block_vector.py @@ -204,6 +204,7 @@ def test_nonzero(self): for bid, blk in enumerate(n[0]): self.assertTrue(np.allclose(blk, v2.get_block(bid))) + @unittest.skipUnless(np.__version__[0] == "1", "PTP only included in Numpy 1.x") def test_ptp(self): v = BlockVector(2) a = np.arange(5) From 1a17057a90cc1b5c0f7a3fcea7bae058c4cc29f0 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 6 Nov 2024 22:32:11 -0700 Subject: [PATCH 04/21] BlockVector: add support for reduce ufuncs; refactur unary and binary handlers --- pyomo/contrib/pynumero/sparse/block_vector.py | 104 +++++++----------- .../sparse/tests/test_block_vector.py | 3 + 2 files changed, 43 insertions(+), 64 deletions(-) diff --git a/pyomo/contrib/pynumero/sparse/block_vector.py b/pyomo/contrib/pynumero/sparse/block_vector.py index 531dbaed383..53fa1d6518c 100644 --- a/pyomo/contrib/pynumero/sparse/block_vector.py +++ b/pyomo/contrib/pynumero/sparse/block_vector.py @@ -270,47 +270,49 @@ def __array_wrap__(self, out_arr, context=None): return super(BlockVector, self).__array_wrap__(self, out_arr, context) def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): + """Runs ufuncs specializations to BlockVector""" + if kwargs.get('out', None) is not None: + return NotImplemented + if method == 'reduce' and ufunc in vec_associative_reductions: + (arg,) = inputs + return self._reduction_operation(ufunc, method, arg, kwargs) + if method == '__call__': + if ufunc in vec_unary_ufuncs: + (arg,) = inputs + return self._unary_operation(ufunc, method, arg, kwargs) + if ufunc in vec_binary_ufuncs: + return self._binary_operation(ufunc, method, inputs, kwargs) + return NotImplemented + + def _reduction_operation(self, ufunc, method, x, kwargs): + results = [ + self._unary_operation(ufunc, method, x.get_block(i), kwargs) + for i in range(x.nblocks) ] - - args = [input_ for i, input_ in enumerate(inputs)] - outputs = kwargs.pop('out', None) - if outputs is not None: - raise NotImplementedError( - str(ufunc) - + ' cannot be used with BlockVector if the out keyword argument is given.' - ) - - if ufunc in unary_funcs: - results = self._unary_operation(ufunc, method, *args, **kwargs) - return results - elif ufunc in binary_funcs: - results = self._binary_operation(ufunc, method, *args, **kwargs) - return results + if len(results) == 1: + return results[0] else: - raise NotImplementedError(str(ufunc) + "not supported for BlockVector") + return super().__array_ufunc__(ufunc, method, np.array(results), **kwargs) - def _unary_operation(self, ufunc, method, *args, **kwargs): + def _unary_operation(self, ufunc, method, x, kwargs): """Run recursion to perform unary_funcs on BlockVector""" # ToDo: deal with out - x = args[0] if isinstance(x, BlockVector): v = BlockVector(x.nblocks) for i in range(x.nblocks): - _args = [x.get_block(i)] + [args[j] for j in range(1, len(args))] - v.set_block(i, self._unary_operation(ufunc, method, *_args, **kwargs)) + v.set_block( + i, self._unary_operation(ufunc, method, x.get_block(i), kwargs) + ) return v elif type(x) == np.ndarray: - return super(BlockVector, self).__array_ufunc__( - ufunc, method, *args, **kwargs - ) + return super().__array_ufunc__(ufunc, method, x, **kwargs) else: - raise NotImplementedError() + return NotImplemented - def _binary_operation(self, ufunc, method, *args, **kwargs): + def _binary_operation(self, ufunc, method, args, kwargs): """Run recursion to perform binary_funcs on BlockVector""" # ToDo: deal with out - x1 = args[0] - x2 = args[1] + x1, x2 = args if isinstance(x1, BlockVector) and isinstance(x2, BlockVector): assert_block_structure(x1) assert_block_structure(x2) @@ -323,14 +325,8 @@ def _binary_operation(self, ufunc, method, *args, **kwargs): res = BlockVector(x1.nblocks) for i in range(x1.nblocks): - _args = ( - [x1.get_block(i)] - + [x2.get_block(i)] - + [args[j] for j in range(2, len(args))] - ) - res.set_block( - i, self._binary_operation(ufunc, method, *_args, **kwargs) - ) + _args = (x1.get_block(i), x2.get_block(i)) + res.set_block(i, self._binary_operation(ufunc, method, _args, kwargs)) return res elif type(x1) == np.ndarray and isinstance(x2, BlockVector): assert_block_structure(x2) @@ -341,14 +337,8 @@ def _binary_operation(self, ufunc, method, *args, **kwargs): accum = 0 for i in range(x2.nblocks): nelements = x2._brow_lengths[i] - _args = ( - [x1[accum : accum + nelements]] - + [x2.get_block(i)] - + [args[j] for j in range(2, len(args))] - ) - res.set_block( - i, self._binary_operation(ufunc, method, *_args, **kwargs) - ) + _args = (x1[accum : accum + nelements], x2.get_block(i)) + res.set_block(i, self._binary_operation(ufunc, method, _args, kwargs)) accum += nelements return res elif type(x2) == np.ndarray and isinstance(x1, BlockVector): @@ -360,37 +350,23 @@ def _binary_operation(self, ufunc, method, *args, **kwargs): accum = 0 for i in range(x1.nblocks): nelements = x1._brow_lengths[i] - _args = ( - [x1.get_block(i)] - + [x2[accum : accum + nelements]] - + [args[j] for j in range(2, len(args))] - ) - res.set_block( - i, self._binary_operation(ufunc, method, *_args, **kwargs) - ) + _args = (x1.get_block(i), x2[accum : accum + nelements]) + res.set_block(i, self._binary_operation(ufunc, method, _args, kwargs)) accum += nelements return res elif np.isscalar(x1) and isinstance(x2, BlockVector): assert_block_structure(x2) res = BlockVector(x2.nblocks) for i in range(x2.nblocks): - _args = ( - [x1] + [x2.get_block(i)] + [args[j] for j in range(2, len(args))] - ) - res.set_block( - i, self._binary_operation(ufunc, method, *_args, **kwargs) - ) + _args = (x1, x2.get_block(i)) + res.set_block(i, self._binary_operation(ufunc, method, _args, kwargs)) return res elif np.isscalar(x2) and isinstance(x1, BlockVector): assert_block_structure(x1) res = BlockVector(x1.nblocks) for i in range(x1.nblocks): - _args = ( - [x1.get_block(i)] + [x2] + [args[j] for j in range(2, len(args))] - ) - res.set_block( - i, self._binary_operation(ufunc, method, *_args, **kwargs) - ) + _args = (x1.get_block(i), x2) + res.set_block(i, self._binary_operation(ufunc, method, _args, kwargs)) return res elif (type(x1) == np.ndarray or np.isscalar(x1)) and ( type(x2) == np.ndarray or np.isscalar(x2) @@ -403,7 +379,7 @@ def _binary_operation(self, ufunc, method, *args, **kwargs): raise RuntimeError('Operation not supported by BlockVector') if x2.__class__.__name__ == 'MPIBlockVector': raise RuntimeError('Operation not supported by BlockVector') - raise NotImplementedError() + return NotImplemented @property def nblocks(self): diff --git a/pyomo/contrib/pynumero/sparse/tests/test_block_vector.py b/pyomo/contrib/pynumero/sparse/tests/test_block_vector.py index 78d69bc91cd..397dac04113 100644 --- a/pyomo/contrib/pynumero/sparse/tests/test_block_vector.py +++ b/pyomo/contrib/pynumero/sparse/tests/test_block_vector.py @@ -1073,6 +1073,9 @@ def test_reduce_ufuncs(self): v.set_block(0, a) v.set_block(1, b) + for fun in vec_associative_reductions: + self.assertAlmostEqual(fun.reduce(v), fun.reduce(v.flatten())) + reduce_funcs = [np.sum, np.max, np.min, np.prod, np.mean] for fun in reduce_funcs: self.assertAlmostEqual(fun(v), fun(v.flatten())) From 2f6e7d6980f39acc8d8d129697a23f9e0b7b1296 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 6 Nov 2024 22:39:16 -0700 Subject: [PATCH 05/21] Add GHA test for numpy2 --- .github/workflows/test_pr_and_main.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 0eba6d96310..967b221c3e3 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -141,6 +141,15 @@ jobs: TARGET: linux PYENV: pip + - os: ubuntu-latest + python: 3.13 + other: /numpy2 + slim: 1 + skip_doctest: 1 + TARGET: linux + PYENV: pip + PACKAGES: 'numpy>2.0' scipy networkx dill gurobipy + - os: ubuntu-latest python: 3.9 other: /pyutilib From a2f27b2cb9e2c5e5dbfe0ca01f994aef0ced38d9 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 6 Nov 2024 23:05:37 -0700 Subject: [PATCH 06/21] Update MPIBlockVector testing --- .../sparse/tests/test_mpi_block_vector.py | 79 +++++-------------- 1 file changed, 18 insertions(+), 61 deletions(-) diff --git a/pyomo/contrib/pynumero/sparse/tests/test_mpi_block_vector.py b/pyomo/contrib/pynumero/sparse/tests/test_mpi_block_vector.py index c28c524823a..25230662802 100644 --- a/pyomo/contrib/pynumero/sparse/tests/test_mpi_block_vector.py +++ b/pyomo/contrib/pynumero/sparse/tests/test_mpi_block_vector.py @@ -1403,48 +1403,13 @@ def test_unary_ufuncs(self): bv.set_block(0, a) bv.set_block(1, b) - unary_funcs = [ - np.log10, - np.sin, - np.cos, - np.exp, - np.ceil, - np.floor, - np.tan, - np.arctan, - np.arcsin, - np.arccos, - np.sinh, - np.cosh, - np.abs, - np.tanh, - np.arcsinh, - np.arctanh, - np.fabs, - np.sqrt, - np.log, - np.log2, - np.absolute, - np.isfinite, - np.isinf, - np.isnan, - np.log1p, - np.logical_not, - np.exp2, - np.expm1, - np.sign, - np.rint, - np.square, - np.positive, - np.negative, - np.rad2deg, - np.deg2rad, - np.conjugate, - np.reciprocal, - ] + _int_ufuncs = {np.invert, np.arccosh} bv2 = BlockVector(2) for fun in unary_funcs: + if fun in _int_ufuncs: + continue + bv2.set_block(0, fun(bv.get_block(0))) bv2.set_block(1, fun(bv.get_block(1))) res = fun(v) @@ -1454,7 +1419,7 @@ def test_unary_ufuncs(self): self.assertTrue(np.allclose(res.get_block(i), bv2.get_block(i))) with self.assertRaises(Exception) as context: - np.cbrt(v) + np.modf(v) with self.assertRaises(Exception) as context: np.cumsum(v) @@ -1504,29 +1469,21 @@ def test_binary_ufuncs(self): bv2.set_block(0, np.ones(3) * 3.0) bv2.set_block(1, np.ones(2) * 2.8) - binary_ufuncs = [ - np.add, - np.multiply, - np.divide, - np.subtract, - np.greater, - np.greater_equal, - np.less, - np.less_equal, - np.not_equal, - np.maximum, - np.minimum, - np.fmax, - np.fmin, - np.equal, - np.logaddexp, - np.logaddexp2, - np.remainder, - np.heaviside, - np.hypot, - ] + _int_ufuncs = { + np.gcd, + np.lcm, + np.ldexp, + np.left_shift, + np.right_shift, + np.bitwise_and, + np.bitwise_or, + np.bitwise_xor, + } for fun in binary_ufuncs: + if fun in _int_ufuncs: + continue + serial_res = fun(bv, bv2) res = fun(v, v2) From 1ea3c8c4dd5687a3a52378ed1435a1ec02989607 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 6 Nov 2024 23:07:04 -0700 Subject: [PATCH 07/21] Attempt to get NumPy2 GHA job running --- .github/workflows/test_pr_and_main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 967b221c3e3..60148eb2224 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -148,7 +148,7 @@ jobs: skip_doctest: 1 TARGET: linux PYENV: pip - PACKAGES: 'numpy>2.0' scipy networkx dill gurobipy + PACKAGES: numpy scipy networkx dill gurobipy - os: ubuntu-latest python: 3.9 From 23668fcee463a711c8f7026880f6f8e5502c831f Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 6 Nov 2024 23:15:29 -0700 Subject: [PATCH 08/21] Back off Python version so gurobipy installs --- .github/workflows/test_pr_and_main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 60148eb2224..8159b748321 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -142,7 +142,7 @@ jobs: PYENV: pip - os: ubuntu-latest - python: 3.13 + python: 3.12 other: /numpy2 slim: 1 skip_doctest: 1 From 6841f062743256d0c8cefc1e3fe3040b1523d2e1 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 6 Nov 2024 23:16:32 -0700 Subject: [PATCH 09/21] Another attempt at fixing numpy version --- .github/workflows/test_pr_and_main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 8159b748321..46e05d1fc8d 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -148,7 +148,7 @@ jobs: skip_doctest: 1 TARGET: linux PYENV: pip - PACKAGES: numpy scipy networkx dill gurobipy + PACKAGES: "gurobipy dill 'numpy>2.0' scipy networkx" - os: ubuntu-latest python: 3.9 From 1345666eab433346c64a6a6ea9a4fa4680f39c57 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 6 Nov 2024 23:19:27 -0700 Subject: [PATCH 10/21] Add missing imports --- pyomo/contrib/pynumero/sparse/tests/test_mpi_block_vector.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyomo/contrib/pynumero/sparse/tests/test_mpi_block_vector.py b/pyomo/contrib/pynumero/sparse/tests/test_mpi_block_vector.py index 25230662802..91a71e51329 100644 --- a/pyomo/contrib/pynumero/sparse/tests/test_mpi_block_vector.py +++ b/pyomo/contrib/pynumero/sparse/tests/test_mpi_block_vector.py @@ -35,6 +35,10 @@ if not SKIPTESTS: from pyomo.contrib.pynumero.sparse import BlockVector + from pyomo.contrib.pynumero.sparse.base_block import ( + vec_unary_ufuncs, + vec_binary_ufuncs, + ) from pyomo.contrib.pynumero.sparse.mpi_block_vector import MPIBlockVector From 045d3a671097af5a939113702a76f59ef5b8ed8f Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 6 Nov 2024 23:23:03 -0700 Subject: [PATCH 11/21] Update numpy version specification --- .github/workflows/test_pr_and_main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 46e05d1fc8d..db75f2e795a 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -148,7 +148,7 @@ jobs: skip_doctest: 1 TARGET: linux PYENV: pip - PACKAGES: "gurobipy dill 'numpy>2.0' scipy networkx" + PACKAGES: "gurobipy dill numpy>2.0 scipy networkx" - os: ubuntu-latest python: 3.9 From 019e849c01a480a6885470074019e55b559eb886 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 6 Nov 2024 23:23:17 -0700 Subject: [PATCH 12/21] Add missing imports --- pyomo/contrib/pynumero/sparse/tests/test_mpi_block_vector.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyomo/contrib/pynumero/sparse/tests/test_mpi_block_vector.py b/pyomo/contrib/pynumero/sparse/tests/test_mpi_block_vector.py index 91a71e51329..c308b99a0b1 100644 --- a/pyomo/contrib/pynumero/sparse/tests/test_mpi_block_vector.py +++ b/pyomo/contrib/pynumero/sparse/tests/test_mpi_block_vector.py @@ -1410,7 +1410,7 @@ def test_unary_ufuncs(self): _int_ufuncs = {np.invert, np.arccosh} bv2 = BlockVector(2) - for fun in unary_funcs: + for fun in vec_unary_ufuncs: if fun in _int_ufuncs: continue @@ -1484,7 +1484,7 @@ def test_binary_ufuncs(self): np.bitwise_xor, } - for fun in binary_ufuncs: + for fun in vec_binary_ufuncs: if fun in _int_ufuncs: continue From f0c2e9c9437ced9e121485187c47960b6068565f Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 6 Nov 2024 23:47:04 -0700 Subject: [PATCH 13/21] Switch decorator order so tests are correctly skipped --- pyomo/contrib/pyros/tests/test_uncertainty_sets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pyros/tests/test_uncertainty_sets.py b/pyomo/contrib/pyros/tests/test_uncertainty_sets.py index e6ad46ce740..b70991efed6 100644 --- a/pyomo/contrib/pyros/tests/test_uncertainty_sets.py +++ b/pyomo/contrib/pyros/tests/test_uncertainty_sets.py @@ -779,7 +779,6 @@ def test_error_on_rank_deficient_psi_mat(self): beta=1 / 6, ) - @unittest.skipUnless(baron_available, "BARON is not available") @parameterized.expand( [ # map beta to expected parameter bounds @@ -813,6 +812,7 @@ def test_error_on_rank_deficient_psi_mat(self): ["beta1", 1, [(-3.0, 3.0), (-1.1, 3.1), (-13.0, 17.0), (-12.0, 18.0)]], ] ) + @unittest.skipUnless(baron_available, "BARON is not available") def test_compute_parameter_bounds(self, name, beta, expected_param_bounds): """ Test parameter bounds computations give expected results. From fa2d7bd4693d07e5aca8923e66259e571ac3e6df Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 6 Nov 2024 23:56:11 -0700 Subject: [PATCH 14/21] Add missing guard for baron availability --- pyomo/contrib/pyros/tests/test_uncertainty_sets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/contrib/pyros/tests/test_uncertainty_sets.py b/pyomo/contrib/pyros/tests/test_uncertainty_sets.py index b70991efed6..13195b3270e 100644 --- a/pyomo/contrib/pyros/tests/test_uncertainty_sets.py +++ b/pyomo/contrib/pyros/tests/test_uncertainty_sets.py @@ -2401,6 +2401,7 @@ def test_set_as_constraint(self): self.assertEqual(len(uq.uncertainty_cons), 3) self.assertEqual(len(uq.uncertain_param_vars), 2) + @unittest.skipUnless(baron_available, "BARON is not available") def test_compute_parameter_bounds(self): """ Test parameter bounds computations give expected results. From 5b08054def9507df578e9806ac36216b7527c0ee Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Nov 2024 08:41:23 -0700 Subject: [PATCH 15/21] Clean up/simplify imports --- pyomo/gdp/tests/test_hull.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pyomo/gdp/tests/test_hull.py b/pyomo/gdp/tests/test_hull.py index 8a780d4b988..e495a4af791 100644 --- a/pyomo/gdp/tests/test_hull.py +++ b/pyomo/gdp/tests/test_hull.py @@ -9,10 +9,15 @@ # This software is distributed under the 3-clause BSD License. # ___________________________________________________________________________ -from pyomo.common.dependencies import dill_available +import logging +import random +from io import StringIO + import pyomo.common.unittest as unittest + +from pyomo.common.dependencies import dill_available from pyomo.common.log import LoggingIntercept -import logging +from pyomo.common.fileutils import this_file_dir from pyomo.environ import ( TransformationFactory, @@ -46,14 +51,9 @@ import pyomo.gdp.tests.models as models import pyomo.gdp.tests.common_tests as ct -import random -from io import StringIO -import os -from os.path import abspath, dirname, join -currdir = dirname(abspath(__file__)) -from filecmp import cmp +currdir = this_file_dir() EPS = TransformationFactory('gdp.hull').CONFIG.EPS linear_solvers = ct.linear_solvers From 023dda9df06898d5bc91eb0e6a3c28dd1c22f6e8 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Nov 2024 08:41:31 -0700 Subject: [PATCH 16/21] Bump recursion limit --- pyomo/gdp/tests/test_hull.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pyomo/gdp/tests/test_hull.py b/pyomo/gdp/tests/test_hull.py index e495a4af791..5cf1718f168 100644 --- a/pyomo/gdp/tests/test_hull.py +++ b/pyomo/gdp/tests/test_hull.py @@ -10,6 +10,7 @@ # ___________________________________________________________________________ import logging +import sys import random from io import StringIO @@ -2874,7 +2875,14 @@ def test_pickle(self): @unittest.skipIf(not dill_available, "Dill is not available") def test_dill_pickle(self): - ct.check_transformed_model_pickles_with_dill(self, 'hull') + try: + # As of Nov 2024, this test needs a larger recursion limit + # due to the various references among the modeling objects + rl = sys.getrecursionlimit() + sys.setrecursionlimit(1385) + ct.check_transformed_model_pickles_with_dill(self, 'hull') + finally: + sys.setrecursionlimit(rl) @unittest.skipUnless(gurobi_available, "Gurobi is not available") From 594c455566a896fe88c51d6ce18ca0f85167469e Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Nov 2024 08:44:31 -0700 Subject: [PATCH 17/21] NFC: apply black --- pyomo/gdp/tests/test_hull.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyomo/gdp/tests/test_hull.py b/pyomo/gdp/tests/test_hull.py index 5cf1718f168..3b37501ed9e 100644 --- a/pyomo/gdp/tests/test_hull.py +++ b/pyomo/gdp/tests/test_hull.py @@ -53,7 +53,6 @@ import pyomo.gdp.tests.common_tests as ct - currdir = this_file_dir() EPS = TransformationFactory('gdp.hull').CONFIG.EPS From 14fcc65edecfdef4b829be0d06d6b78b7ca25100 Mon Sep 17 00:00:00 2001 From: Miranda Mundt <55767766+mrmundt@users.noreply.github.com> Date: Thu, 7 Nov 2024 08:51:01 -0700 Subject: [PATCH 18/21] Fix typo --- pyomo/contrib/pynumero/sparse/base_block.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyomo/contrib/pynumero/sparse/base_block.py b/pyomo/contrib/pynumero/sparse/base_block.py index 0cbeaabfacc..1baa10e1f73 100644 --- a/pyomo/contrib/pynumero/sparse/base_block.py +++ b/pyomo/contrib/pynumero/sparse/base_block.py @@ -258,7 +258,7 @@ def tostring(self, order='C'): np.remainder, np.mod, np.fmod, - # np.divmod, # dieabled because shape is not preserved + # np.divmod, # disabled because shape is not preserved np.heaviside, np.gcd, np.lcm, From fb2105c1345bb145ae08aff7e9fba20182c65f7d Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Nov 2024 09:18:31 -0700 Subject: [PATCH 19/21] Add debugging --- pyomo/gdp/tests/test_hull.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/gdp/tests/test_hull.py b/pyomo/gdp/tests/test_hull.py index 3b37501ed9e..7b464f20bb6 100644 --- a/pyomo/gdp/tests/test_hull.py +++ b/pyomo/gdp/tests/test_hull.py @@ -2878,6 +2878,7 @@ def test_dill_pickle(self): # As of Nov 2024, this test needs a larger recursion limit # due to the various references among the modeling objects rl = sys.getrecursionlimit() + print(f"RECURSIONLIMIT {rl}") sys.setrecursionlimit(1385) ct.check_transformed_model_pickles_with_dill(self, 'hull') finally: From bed3457a232fbeb7670ea27f25119046fa93d7a2 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Nov 2024 09:34:31 -0700 Subject: [PATCH 20/21] More debugging --- pyomo/gdp/tests/test_hull.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/gdp/tests/test_hull.py b/pyomo/gdp/tests/test_hull.py index 7b464f20bb6..f091a0f4bb3 100644 --- a/pyomo/gdp/tests/test_hull.py +++ b/pyomo/gdp/tests/test_hull.py @@ -2883,6 +2883,7 @@ def test_dill_pickle(self): ct.check_transformed_model_pickles_with_dill(self, 'hull') finally: sys.setrecursionlimit(rl) + self.fail() @unittest.skipUnless(gurobi_available, "Gurobi is not available") From b729886eb6242134ecc2b6b5158a58e45c666e9c Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 7 Nov 2024 10:09:51 -0700 Subject: [PATCH 21/21] Update recursionlimit to not lower it if it was set elsewhere --- pyomo/gdp/tests/test_hull.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pyomo/gdp/tests/test_hull.py b/pyomo/gdp/tests/test_hull.py index f091a0f4bb3..ec011fb802a 100644 --- a/pyomo/gdp/tests/test_hull.py +++ b/pyomo/gdp/tests/test_hull.py @@ -2877,13 +2877,12 @@ def test_dill_pickle(self): try: # As of Nov 2024, this test needs a larger recursion limit # due to the various references among the modeling objects + # 1385 is sufficient locally, but not always on GHA. rl = sys.getrecursionlimit() - print(f"RECURSIONLIMIT {rl}") - sys.setrecursionlimit(1385) + sys.setrecursionlimit(max(1500, rl)) ct.check_transformed_model_pickles_with_dill(self, 'hull') finally: sys.setrecursionlimit(rl) - self.fail() @unittest.skipUnless(gurobi_available, "Gurobi is not available")