diff --git a/227.patch b/227.patch new file mode 100644 index 0000000..849e97c --- /dev/null +++ b/227.patch @@ -0,0 +1,659 @@ +From d4e986af8f4bab6ae64153f5803cdb8c0bd98d9f Mon Sep 17 00:00:00 2001 +From: Christoph Hansknecht +Date: Sat, 23 Sep 2023 13:25:36 +0200 +Subject: [PATCH 1/2] Ensure that all cyipopt callbacks are "noexcept" + +--- + cyipopt/cython/ipopt_wrapper.pyx | 513 ++++++++++++++++++------------- + 1 file changed, 298 insertions(+), 215 deletions(-) + +diff --git a/cyipopt/cython/ipopt_wrapper.pyx b/cyipopt/cython/ipopt_wrapper.pyx +index e0737f2..d1a3e6d 100644 +--- a/cyipopt/cython/ipopt_wrapper.pyx ++++ b/cyipopt/cython/ipopt_wrapper.pyx +@@ -858,16 +858,20 @@ cdef Bool objective_cb(Index n, + Bool new_x, + Number* obj_value, + UserDataPtr user_data +- ): +- +- log(b"objective_cb", logging.INFO) +- +- cdef object self = user_data ++ ) noexcept: ++ cdef Problem self + cdef Index i +- cdef np.ndarray[DTYPEd_t, ndim=1] _x = np.zeros((n,), dtype=DTYPEd) +- for i in range(n): +- _x[i] = x[i] ++ cdef np.ndarray[DTYPEd_t, ndim=1] _x ++ + try: ++ log(b"objective_cb", logging.INFO) ++ ++ self = user_data ++ _x = np.zeros((n,), dtype=DTYPEd) ++ ++ for i in range(n): ++ _x[i] = x[i] ++ + obj_value[0] = self.__objective(_x) + except CyIpoptEvaluationError: + return False +@@ -881,31 +885,35 @@ cdef Bool gradient_cb(Index n, + Bool new_x, + Number* grad_f, + UserDataPtr user_data +- ): ++ ) noexcept: + +- log(b"gradient_cb", logging.INFO) +- +- cdef object self = user_data ++ cdef Problem self + cdef Index i +- cdef np.ndarray[DTYPEd_t, ndim=1] _x = np.zeros((n,), dtype=DTYPEd) ++ cdef np.ndarray[DTYPEd_t, ndim=1] _x + cdef np.ndarray[DTYPEd_t, ndim=1] np_grad_f + +- for i in range(n): +- _x[i] = x[i] +- + try: ++ log(b"gradient_cb", logging.INFO) ++ ++ self = user_data ++ _x = np.zeros((n,), dtype=DTYPEd) ++ ++ for i in range(n): ++ _x[i] = x[i] ++ + ret_val = self.__gradient(_x) ++ ++ np_grad_f = np.array(ret_val, dtype=DTYPEd).flatten() ++ ++ for i in range(n): ++ grad_f[i] = np_grad_f[i] ++ + except CyIpoptEvaluationError: + return False + except: + self.__exception = sys.exc_info() + return True + +- np_grad_f = np.array(ret_val, dtype=DTYPEd).flatten() +- +- for i in range(n): +- grad_f[i] = np_grad_f[i] +- + return True + + +@@ -915,34 +923,125 @@ cdef Bool constraints_cb(Index n, + Index m, + Number* g, + UserDataPtr user_data +- ): ++ ) noexcept: ++ cdef Problem self ++ cdef Index i ++ cdef np.ndarray[DTYPEd_t, ndim=1] _x ++ cdef np.ndarray[DTYPEd_t, ndim=1] np_g ++ ++ try: ++ log(b"constraints_cb", logging.INFO) ++ ++ self = user_data ++ _x = np.zeros((n,), dtype=DTYPEd) ++ ++ if not self.__constraints: ++ log(b"Constraints callback not defined", logging.DEBUG) ++ return True ++ ++ for i in range(n): ++ _x[i] = x[i] ++ ++ ret_val = self.__constraints(_x) ++ ++ np_g = np.array(ret_val, dtype=DTYPEd).flatten() ++ ++ for i in range(m): ++ g[i] = np_g[i] + +- log(b"constraints_cb", logging.INFO) ++ except CyIpoptEvaluationError: ++ return False ++ except: ++ self.__exception = sys.exc_info() ++ return True + +- cdef object self = user_data ++ return True ++ ++ ++cdef Bool jacobian_struct_cb(Index n, ++ Index m, ++ Index nele_jac, ++ Index *iRow, ++ Index *jCol, ++ UserDataPtr user_data): ++ cdef Problem self = user_data ++ cdef Index i ++ ++ if not self.__jacobianstructure: ++ msg = b"Jacobian callback not defined. assuming a dense jacobian" ++ log(msg, logging.INFO) ++ ++ # ++ # Assuming a dense Jacobian ++ # ++ s = np.unravel_index(np.arange(self.__m * self.__n), ++ (self.__m, self.__n)) ++ np_iRow = np.array(s[0], dtype=DTYPEi) ++ np_jCol = np.array(s[1], dtype=DTYPEi) ++ else: ++ # ++ # Sparse Jacobian ++ # ++ ret_val = self.__jacobianstructure() ++ ++ np_iRow = np.array(ret_val[0], dtype=DTYPEi).flatten() ++ np_jCol = np.array(ret_val[1], dtype=DTYPEi).flatten() ++ ++ if (np_iRow.size != nele_jac) or (np_jCol.size != nele_jac): ++ msg = b"Invalid number of indices returned from jacobianstructure" ++ log(msg, logging.ERROR) ++ return False ++ ++ if (np_iRow < 0).any() or (np_iRow >= m).any(): ++ msg = b"Invalid row indices returned from jacobianstructure" ++ log(msg, logging.ERROR) ++ return False ++ ++ if (np_jCol < 0).any() or (np_jCol >= n).any(): ++ msg = b"Invalid column indices returned from jacobianstructure" ++ log(msg, logging.ERROR) ++ return False ++ ++ for i in range(nele_jac): ++ iRow[i] = np_iRow[i] ++ jCol[i] = np_jCol[i] ++ ++ return True ++ ++ ++cdef Bool jacobian_value_cb(Index n, ++ Number* x, ++ Bool new_x, ++ Index m, ++ Index nele_jac, ++ Number *values, ++ UserDataPtr user_data ++ ): ++ cdef Problem self = user_data + cdef Index i + cdef np.ndarray[DTYPEd_t, ndim=1] _x = np.zeros((n,), dtype=DTYPEd) +- cdef np.ndarray[DTYPEd_t, ndim=1] np_g + +- if not self.__constraints: +- log(b"Constraints callback not defined", logging.DEBUG) ++ if not self.__jacobian: ++ log(b"Jacobian callback not defined", logging.DEBUG) + return True + + for i in range(n): + _x[i] = x[i] + + try: +- ret_val = self.__constraints(_x) ++ ret_val = self.__jacobian(_x) + except CyIpoptEvaluationError: +- return False +- except: +- self.__exception = sys.exc_info() +- return True ++ return False + +- np_g = np.array(ret_val, dtype=DTYPEd).flatten() ++ np_jac_g = np.array(ret_val, dtype=DTYPEd).flatten() + +- for i in range(m): +- g[i] = np_g[i] ++ if (np_jac_g.size != nele_jac): ++ msg = b"Invalid number of indices returned from jacobian" ++ log(msg, logging.ERROR) ++ return False ++ ++ for i in range(nele_jac): ++ values[i] = np_jac_g[i] + + return True + +@@ -956,89 +1055,134 @@ cdef Bool jacobian_cb(Index n, + Index *jCol, + Number *values, + UserDataPtr user_data +- ): ++ ) noexcept: ++ cdef Problem self ++ cdef object ret_val + +- log(b"jacobian_cb", logging.INFO) ++ try: ++ log(b"jacobian_cb", logging.INFO) ++ ret_val = True ++ self = user_data ++ if values == NULL: ++ log(b"Querying for iRow/jCol indices of the jacobian", logging.INFO) ++ ret_val = jacobian_struct_cb(n, m, nele_jac, iRow, jCol, user_data) ++ else: ++ log(b"Querying for jacobian", logging.INFO) ++ ret_val = jacobian_value_cb(n, x, new_x, m, nele_jac, values, user_data) + +- cdef object self = user_data ++ except: ++ self.__exception = sys.exc_info() ++ finally: ++ return ret_val ++ ++ ++cdef Bool hessian_struct_cb(Index n, ++ Index m, ++ Index nele_hess, ++ Index *iRow, ++ Index *jCol, ++ UserDataPtr user_data ++ ): ++ cdef Problem self = user_data + cdef Index i +- cdef np.ndarray[DTYPEd_t, ndim=1] _x = np.zeros((n,), dtype=DTYPEd) + cdef np.ndarray[DTYPEi_t, ndim=1] np_iRow + cdef np.ndarray[DTYPEi_t, ndim=1] np_jCol +- cdef np.ndarray[DTYPEd_t, ndim=1] np_jac_g + +- if values == NULL: +- log(b"Querying for iRow/jCol indices of the jacobian", logging.INFO) ++ msg = b"Querying for iRow/jCol indices of the Hessian" ++ log(msg, logging.INFO) + +- if not self.__jacobianstructure: +- msg = b"Jacobian callback not defined. assuming a dense jacobian" +- log(msg, logging.INFO) ++ if not self.__hessianstructure: ++ msg = (b"Hessian callback not defined. assuming a lower triangle " ++ b"Hessian") ++ log(msg, logging.INFO) + +- # +- # Assuming a dense Jacobian +- # +- s = np.unravel_index(np.arange(self.__m * self.__n), +- (self.__m, self.__n)) +- np_iRow = np.array(s[0], dtype=DTYPEi) +- np_jCol = np.array(s[1], dtype=DTYPEi) +- else: +- # +- # Sparse Jacobian +- # +- try: +- ret_val = self.__jacobianstructure() +- except: +- self.__exception = sys.exc_info() +- return True +- +- np_iRow = np.array(ret_val[0], dtype=DTYPEi).flatten() +- np_jCol = np.array(ret_val[1], dtype=DTYPEi).flatten() +- +- if (np_iRow.size != nele_jac) or (np_jCol.size != nele_jac): +- msg = b"Invalid number of indices returned from jacobianstructure" +- log(msg, logging.ERROR) +- return False +- +- if (np_iRow < 0).any() or (np_iRow >= m).any(): +- msg = b"Invalid row indices returned from jacobianstructure" +- log(msg, logging.ERROR) +- return False +- +- if (np_jCol < 0).any() or (np_jCol >= n).any(): +- msg = b"Invalid column indices returned from jacobianstructure" +- log(msg, logging.ERROR) +- return False +- +- for i in range(nele_jac): +- iRow[i] = np_iRow[i] +- jCol[i] = np_jCol[i] ++ # ++ # Assuming a lower triangle Hessian ++ # Note: ++ # There is a need to reconvert the s.col and s.row to arrays ++ # because they have the wrong stride ++ # ++ row, col = np.nonzero(np.tril(np.ones((self.__n, self.__n)))) ++ np_iRow = np.array(col, dtype=DTYPEi) ++ np_jCol = np.array(row, dtype=DTYPEi) + else: +- log(b"Querying for jacobian", logging.INFO) ++ # ++ # Sparse Hessian ++ # ++ ret_val = self.__hessianstructure() + +- if not self.__jacobian: +- log(b"Jacobian callback not defined", logging.DEBUG) +- return True ++ np_iRow = np.array(ret_val[0], dtype=DTYPEi).flatten() ++ np_jCol = np.array(ret_val[1], dtype=DTYPEi).flatten() + +- for i in range(n): +- _x[i] = x[i] ++ if (np_iRow.size != nele_hess) or (np_jCol.size != nele_hess): ++ msg = b"Invalid number of indices returned from hessianstructure" ++ log(msg, logging.ERROR) ++ return False + +- try: +- ret_val = self.__jacobian(_x) +- except CyIpoptEvaluationError: ++ if not(np_iRow >= np_jCol).all(): ++ msg = b"Indices are not lower triangular in hessianstructure" ++ log(msg, logging.ERROR) + return False +- except: +- self.__exception = sys.exc_info() +- return True + +- np_jac_g = np.array(ret_val, dtype=DTYPEd).flatten() ++ if (np_jCol < 0).any(): ++ msg = b"Invalid column indices returned from hessianstructure" ++ log(msg, logging.ERROR) ++ return False + +- if (np_jac_g.size != nele_jac): +- msg = b"Invalid number of indices returned from jacobian" ++ if (np_iRow >= n).any(): ++ msg = b"Invalid row indices returned from hessianstructure" + log(msg, logging.ERROR) + return False + +- for i in range(nele_jac): +- values[i] = np_jac_g[i] ++ for i in range(nele_hess): ++ iRow[i] = np_iRow[i] ++ jCol[i] = np_jCol[i] ++ ++ return True ++ ++ ++cdef Bool hessian_value_cb(Index n, ++ Number* x, ++ Bool new_x, ++ Number obj_factor, ++ Index m, ++ Number *lambd, ++ Bool new_lambda, ++ Index nele_hess, ++ Number *values, ++ UserDataPtr user_data ++ ): ++ cdef Index i ++ cdef Problem self = user_data ++ cdef np.ndarray[DTYPEd_t, ndim=1] _x = np.zeros((n,), dtype=DTYPEd) ++ cdef np.ndarray[DTYPEd_t, ndim=1] _lambda = np.zeros((m,), dtype=DTYPEd) ++ ++ if not self.__hessian: ++ msg = (b"Hessian callback not defined but called by the Ipopt " ++ b"algorithm") ++ log(msg, logging.ERROR) ++ return False ++ ++ for i in range(n): ++ _x[i] = x[i] ++ ++ for i in range(m): ++ _lambda[i] = lambd[i] ++ ++ try: ++ ret_val = self.__hessian(_x, _lambda, obj_factor) ++ except CyIpoptEvaluationError: ++ return False ++ ++ np_h = np.array(ret_val, dtype=DTYPEd).flatten() ++ ++ if (np_h.size != nele_hess): ++ msg = b"Invalid number of indices returned from hessian" ++ log(msg, logging.ERROR) ++ return False ++ ++ for i in range(nele_hess): ++ values[i] = np_h[i] + + return True + +@@ -1055,104 +1199,38 @@ cdef Bool hessian_cb(Index n, + Index *jCol, + Number *values, + UserDataPtr user_data +- ): ++ ) noexcept: ++ cdef object self ++ cdef object ret_val + +- log(b"hessian_cb", logging.INFO) +- +- cdef object self = user_data +- cdef Index i +- cdef np.ndarray[DTYPEd_t, ndim=1] _x = np.zeros((n,), dtype=DTYPEd) +- cdef np.ndarray[DTYPEd_t, ndim=1] _lambda = np.zeros((m,), dtype=DTYPEd) +- cdef np.ndarray[DTYPEi_t, ndim=1] np_iRow +- cdef np.ndarray[DTYPEi_t, ndim=1] np_jCol +- cdef np.ndarray[DTYPEd_t, ndim=1] np_h +- +- if values == NULL: +- msg = b"Querying for iRow/jCol indices of the Hessian" +- log(msg, logging.INFO) ++ try: ++ log(b"hessian_cb", logging.INFO) ++ ret_val = True ++ self = user_data + +- if not self.__hessianstructure: +- msg = (b"Hessian callback not defined. assuming a lower triangle " +- b"Hessian") +- log(msg, logging.INFO) ++ if values == NULL: ++ ret_val = hessian_struct_cb(n, ++ m, ++ nele_hess, ++ iRow, ++ jCol, ++ user_data) + +- # +- # Assuming a lower triangle Hessian +- # Note: +- # There is a need to reconvert the s.col and s.row to arrays +- # because they have the wrong stride +- # +- row, col = np.nonzero(np.tril(np.ones((self.__n, self.__n)))) +- np_iRow = np.array(col, dtype=DTYPEi) +- np_jCol = np.array(row, dtype=DTYPEi) + else: +- # +- # Sparse Hessian +- # +- try: +- ret_val = self.__hessianstructure() +- except: +- self.__exception = sys.exc_info() +- return True +- +- np_iRow = np.array(ret_val[0], dtype=DTYPEi).flatten() +- np_jCol = np.array(ret_val[1], dtype=DTYPEi).flatten() +- +- if (np_iRow.size != nele_hess) or (np_jCol.size != nele_hess): +- msg = b"Invalid number of indices returned from hessianstructure" +- log(msg, logging.ERROR) +- return False +- +- if not(np_iRow >= np_jCol).all(): +- msg = b"Indices are not lower triangular in hessianstructure" +- log(msg, logging.ERROR) +- return False +- +- if (np_jCol < 0).any(): +- msg = b"Invalid column indices returned from hessianstructure" +- log(msg, logging.ERROR) +- return False +- +- if (np_iRow >= n).any(): +- msg = b"Invalid row indices returned from hessianstructure" +- log(msg, logging.ERROR) +- return False +- +- for i in range(nele_hess): +- iRow[i] = np_iRow[i] +- jCol[i] = np_jCol[i] +- else: +- if not self.__hessian: +- msg = (b"Hessian callback not defined but called by the Ipopt " +- b"algorithm") +- log(msg, logging.ERROR) +- return False +- +- for i in range(n): +- _x[i] = x[i] +- +- for i in range(m): +- _lambda[i] = lambd[i] +- +- try: +- ret_val = self.__hessian(_x, _lambda, obj_factor) +- except CyIpoptEvaluationError: +- return False +- except: +- self.__exception = sys.exc_info() +- return True +- +- np_h = np.array(ret_val, dtype=DTYPEd).flatten() +- +- if (np_h.size != nele_hess): +- msg = b"Invalid number of indices returned from hessian" +- log(msg, logging.ERROR) +- return False +- +- for i in range(nele_hess): +- values[i] = np_h[i] +- +- return True ++ ret_val = hessian_value_cb(n, ++ x, ++ new_x, ++ obj_factor, ++ m, ++ lambd, ++ new_lambda, ++ nele_hess, ++ values, ++ user_data) ++ except: ++ self.__exception = sys.exc_info() ++ finally: ++ return ret_val + + + cdef Bool intermediate_cb(Index alg_mod, +@@ -1167,35 +1245,40 @@ cdef Bool intermediate_cb(Index alg_mod, + Number alpha_pr, + Index ls_trials, + UserDataPtr user_data +- ): ++ ) noexcept: ++ cdef Problem self + +- log(b"intermediate_cb", logging.INFO) ++ try: ++ log(b"intermediate_cb", logging.INFO) + +- cdef object self = user_data ++ self = user_data + +- if self.__exception: +- return False ++ if self.__exception: ++ return False + +- if not self.__intermediate: +- return True ++ if not self.__intermediate: ++ return True + +- ret_val = self.__intermediate(alg_mod, ++ ret_val = self.__intermediate(alg_mod, + iter_count, +- obj_value, +- inf_pr, +- inf_du, +- mu, +- d_norm, +- regularization_size, +- alpha_du, +- alpha_pr, +- ls_trials +- ) +- +- if ret_val is None: ++ obj_value, ++ inf_pr, ++ inf_du, ++ mu, ++ d_norm, ++ regularization_size, ++ alpha_du, ++ alpha_pr, ++ ls_trials ++ ) ++ ++ if ret_val is None: ++ return True ++ except: ++ self.__exception = sys.exc_info() + return True + +- return ret_val ++ return True + + + class problem(Problem): + +From 9b60806dfb51ca06df076b9e073a42012ee80af6 Mon Sep 17 00:00:00 2001 +From: Christoph Hansknecht +Date: Sat, 23 Sep 2023 14:06:23 +0200 +Subject: [PATCH 2/2] Remove cython version restriction + +--- + pyproject.toml | 2 +- + setup.py | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/pyproject.toml b/pyproject.toml +index c85fe42..f4ce20a 100644 +--- a/pyproject.toml ++++ b/pyproject.toml +@@ -1,3 +1,3 @@ + [build-system] +-requires = ["cython>=0.29.28,<3", "oldest-supported-numpy","setuptools>=44.1.1"] ++requires = ["cython>=0.29.28", "oldest-supported-numpy","setuptools>=44.1.1"] + build-backend = "setuptools.build_meta" +diff --git a/setup.py b/setup.py +index a014d06..eb6e1f4 100644 +--- a/setup.py ++++ b/setup.py +@@ -20,7 +20,7 @@ + # install requirements before import + from setuptools import dist + SETUP_REQUIRES = [ +- "cython>=0.29.28,<3", ++ "cython>=0.29.28", + "numpy>=1.21.5", + "setuptools>=44.1.1", + ] diff --git a/python-cyipopt.spec b/python-cyipopt.spec index 87d86b9..2b363f2 100644 --- a/python-cyipopt.spec +++ b/python-cyipopt.spec @@ -22,6 +22,15 @@ URL: https://github.com/mechmotum/cyipopt # the examples. Source: %{url}/archive/v%{version}/cyipopt-%{version}.tar.gz +# Enable support for cython 3 +# https://github.com/mechmotum/cyipopt/pull/227 +# +# Fixes: +# +# Does not build with Cython 3 +# https://github.com/mechmotum/cyipopt/issues/211 +Patch: %{url}/pull/227.patch + # https://fedoraproject.org/wiki/Changes/EncourageI686LeafRemoval ExcludeArch: %{ix86}