diff --git a/CHANGELOG.md b/CHANGELOG.md index 928aff515..3152a4c57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased ### Added ### Fixed +- `Expr.__array_ufunc__` can't handle 0-dim array ### Changed ### Removed diff --git a/src/pyscipopt/expr.pxi b/src/pyscipopt/expr.pxi index cdbed25cd..c625e279c 100644 --- a/src/pyscipopt/expr.pxi +++ b/src/pyscipopt/expr.pxi @@ -203,16 +203,25 @@ cdef class ExprLike: ) if method == "__call__": - if arrays := [a for a in args if isinstance(a, np.ndarray)]: + if arrays := [a for a in args if isinstance(a, np.ndarray) and a.ndim >= 1]: if any(a.dtype.kind not in "fiub" for a in arrays): return NotImplemented # If the np.ndarray is of numeric type, all arguments are converted to # MatrixExpr or MatrixGenExpr and then the ufunc is applied. return ufunc(*[_ensure_matrix(a) for a in args], **kwargs) - # Convert `np.generic` to native Python types to stop __array_ufunc__ - # recursion from `np.generic + MatrixExpr`. - args = [a.item() if isinstance(a, np.generic) else a for a in args] + # Convert `np.generic` and 0-dim `np.ndarray` to native Python types to stop + # __array_ufunc__ recursion from `np.generic + MatrixExpr/Expr` or + # `0-dim np.ndarray + MatrixExpr/Expr`. + args = [ + a.item() + if ( + isinstance(a, np.generic) + or (isinstance(a, np.ndarray) and a.ndim == 0) + ) + else a + for a in args + ] if ufunc is np.add: return args[0] + args[1] diff --git a/tests/test_expr.py b/tests/test_expr.py index 8b8e0ae08..7154ba083 100644 --- a/tests/test_expr.py +++ b/tests/test_expr.py @@ -328,6 +328,34 @@ def test_binary_ufunc(model): assert str(np.greater_equal(a, x)) == "[ExprCons(Expr({Term(x): 1.0}), None, 2.0)]" +def test_np_generic_vs_expr(): + # test #1218 + m = Model() + x = m.addVar(name="x") + value = np.float64(5.0) + + # test <=, np.generic vs Variable + assert str(x <= -value) == "ExprCons(Expr({Term(x): 1.0}), None, -5.0)" + assert str(x <= value) == "ExprCons(Expr({Term(x): 1.0}), None, 5.0)" + assert str(-value <= x) == "ExprCons(Expr({Term(x): 1.0}), -5.0, None)" + assert str(value <= x) == "ExprCons(Expr({Term(x): 1.0}), 5.0, None)" + assert str(np.int64(5) <= x) == "ExprCons(Expr({Term(x): 1.0}), 5.0, None)" + + # test >=, np.generic vs Variable + assert str(value >= x) == "ExprCons(Expr({Term(x): 1.0}), None, 5.0)" + assert str(-value >= x) == "ExprCons(Expr({Term(x): 1.0}), None, -5.0)" + + # test ==, np.generic vs Variable + assert str(value == x) == "ExprCons(Expr({Term(x): 1.0}), 5.0, 5.0)" + + # test <=, 0-ndim int array vs Variable + assert str(np.array(5) <= x) == "ExprCons(Expr({Term(x): 1.0}), 5.0, None)" + + # test <=, 0-ndim Variable array vs Variable + with pytest.raises(TypeError): + 1 <= np.array(x) + + def test_mul(): m = Model() x = m.addVar(name="x")