@@ -55,6 +55,7 @@ from cpython.object cimport Py_LE, Py_EQ, Py_GE, Py_TYPE
5555from cpython.ref cimport PyObject
5656from cpython.tuple cimport PyTuple_GET_ITEM
5757
58+ cimport numpy as cnp
5859from pyscipopt.scip cimport Variable, Solution
5960
6061
@@ -203,16 +204,25 @@ cdef class ExprLike:
203204 )
204205
205206 if method == " __call__" :
206- if arrays := [a for a in args if isinstance (a, np.ndarray)]:
207+ if arrays := [a for a in args if isinstance (a, np.ndarray) and a.ndim >= 1 ]:
207208 if any (a.dtype.kind not in " fiub" for a in arrays):
208209 return NotImplemented
209210 # If the np.ndarray is of numeric type, all arguments are converted to
210211 # MatrixExpr or MatrixGenExpr and then the ufunc is applied.
211212 return ufunc(* [_ensure_matrix(a) for a in args], ** kwargs)
212213
213- # Convert `np.generic` to native Python types to stop __array_ufunc__
214- # recursion from `np.generic + MatrixExpr`.
215- args = [a.item() if isinstance (a, np.generic) else a for a in args]
214+ # Convert `np.generic` and 0-dim `np.ndarray` to native Python types to stop
215+ # __array_ufunc__ recursion from `np.generic + MatrixExpr/Expr` or
216+ # `0-dim np.ndarray + MatrixExpr/Expr`.
217+ args = [
218+ a.item()
219+ if (
220+ isinstance (a, np.generic)
221+ or (isinstance (a, np.ndarray) and a.ndim == 0 )
222+ )
223+ else a
224+ for a in args
225+ ]
216226
217227 if ufunc is np.add:
218228 return args[0 ] + args[1 ]
@@ -291,29 +301,22 @@ cdef class Expr(ExprLike):
291301 if not _is_expr_compatible(other):
292302 return NotImplemented
293303
294- left = self
295- right = other
296- terms = left.terms.copy()
304+ if _is_number(other):
305+ terms = self .terms.copy()
306+ terms[CONST] = terms.get(CONST, 0.0 ) + < double > other
307+ return Expr(terms)
297308
298- if isinstance (right, Expr):
299- # merge the terms by component-wise addition
300- for v,c in right.terms.items():
301- terms[v] = terms.get(v, 0.0 ) + c
302- elif _is_number(right):
303- c = float (right)
304- terms[CONST] = terms.get(CONST, 0.0 ) + c
305- return Expr(terms)
309+ return Expr(_to_dict(self , other, copy = True ))
306310
307311 def __iadd__ (self , other ):
308312 if not _is_expr_compatible(other):
309313 return NotImplemented
310314
311- if isinstance (other, Expr):
312- for v,c in other.terms.items():
313- self .terms[v] = self .terms.get(v, 0.0 ) + c
314- elif _is_number(other):
315- c = float (other)
316- self .terms[CONST] = self .terms.get(CONST, 0.0 ) + c
315+ if _is_number(other):
316+ self .terms[CONST] = self .terms.get(CONST, 0.0 ) + < double > other
317+ else :
318+ _to_dict(self , other, copy = False )
319+
317320 return self
318321
319322 def __mul__ (self , other ):
@@ -1010,6 +1013,26 @@ cdef inline object _ensure_matrix(object arg):
10101013 matrix = MatrixExpr if isinstance (arg, Expr) else MatrixGenExpr
10111014 return np.array(arg, dtype = object ).view(matrix)
10121015
1016+ cdef dict _to_dict(Expr expr, Expr other, bool copy = True ):
1017+ cdef dict children = expr.terms.copy() if copy else expr.terms
1018+ cdef Py_ssize_t pos = < Py_ssize_t> 0
1019+ cdef PyObject* k_ptr = NULL
1020+ cdef PyObject* v_ptr = NULL
1021+ cdef PyObject* old_v_ptr = NULL
1022+ cdef double other_v
1023+ cdef object k_obj
1024+
1025+ while PyDict_Next(other.terms, & pos, & k_ptr, & v_ptr):
1026+ other_v = < double > (< object > v_ptr)
1027+ k_obj = < object > k_ptr
1028+ old_v_ptr = PyDict_GetItem(children, k_obj)
1029+ if old_v_ptr != NULL :
1030+ children[k_obj] = < double > (< object > old_v_ptr) + other_v
1031+ else :
1032+ children[k_obj] = other_v
1033+
1034+ return children
1035+
10131036
10141037def expr_to_nodes (expr ):
10151038 ''' transforms tree to an array of nodes. each node is an operator and the position of the
0 commit comments