From 9cb76f107d4fb7c956acce5065fbf9474c1f1641 Mon Sep 17 00:00:00 2001 From: Ella Wu <602725+ewu63@users.noreply.github.com> Date: Tue, 11 Mar 2025 23:50:40 -0700 Subject: [PATCH 01/16] very preliminary attempt with cyipoptcore --- pyoptsparse/pyIPOPT/pyIPOPT.py | 136 +++++++++++++-------------------- 1 file changed, 52 insertions(+), 84 deletions(-) diff --git a/pyoptsparse/pyIPOPT/pyIPOPT.py b/pyoptsparse/pyIPOPT/pyIPOPT.py index 89f69fb5f..acd7b7a90 100644 --- a/pyoptsparse/pyIPOPT/pyIPOPT.py +++ b/pyoptsparse/pyIPOPT/pyIPOPT.py @@ -5,27 +5,15 @@ # Standard Python modules import copy import datetime -import os import time # External modules +import cyipopt import numpy as np # Local modules from ..pyOpt_optimizer import Optimizer -from ..pyOpt_utils import ( - ICOL, - INFINITY, - IROW, - convertToCOO, - extractRows, - scaleRows, - try_import_compiled_module_from_path, -) - -# import the compiled module -THIS_DIR = os.path.dirname(os.path.abspath(__file__)) -pyipoptcore = try_import_compiled_module_from_path("pyipoptcore", THIS_DIR) +from ..pyOpt_utils import ICOL, INFINITY, IROW, convertToCOO, extractRows, scaleRows class IPOPT(Optimizer): @@ -43,9 +31,6 @@ def __init__(self, raiseError=True, options={}): defOpts = self._getDefaultOptions() informs = self._getInforms() - if isinstance(pyipoptcore, str) and raiseError: - raise ImportError(pyipoptcore) - super().__init__( name, category, @@ -214,73 +199,63 @@ def __call__( jac["coo"][ICOL].copy().astype("int_"), ) - # Define the 4 call back functions that ipopt needs: - def eval_f(x, user_data=None): - fobj, fail = self._masterFunc(x, ["fobj"]) - if fail == 1: - fobj = np.array(np.NaN) - elif fail == 2: - self.userRequestedTermination = True - return fobj - - def eval_g(x, user_data=None): - fcon, fail = self._masterFunc(x, ["fcon"]) - if fail == 1: - fcon = np.array(np.NaN) - elif fail == 2: - self.userRequestedTermination = True - return fcon.copy() - - def eval_grad_f(x, user_data=None): - gobj, fail = self._masterFunc(x, ["gobj"]) - if fail == 1: - gobj = np.array(np.NaN) - elif fail == 2: - self.userRequestedTermination = True - return gobj.copy() - - def eval_jac_g(x, flag, user_data=None): - if flag: - return copy.deepcopy(matStruct) - else: + class CyIPOPTProblem: + # Define the 4 call back functions that ipopt needs: + def objective(_, x): + fobj, fail = self._masterFunc(x, ["fobj"]) + if fail == 1: + fobj = np.array(np.NaN) + elif fail == 2: + self.userRequestedTermination = True + return fobj + + def constraints(_, x): + fcon, fail = self._masterFunc(x, ["fcon"]) + if fail == 1: + fcon = np.array(np.NaN) + elif fail == 2: + self.userRequestedTermination = True + return fcon.copy() + + def gradient(_, x): + gobj, fail = self._masterFunc(x, ["gobj"]) + if fail == 1: + gobj = np.array(np.NaN) + elif fail == 2: + self.userRequestedTermination = True + return gobj.copy() + + def jacobian(_, x): gcon, fail = self._masterFunc(x, ["gcon"]) if fail == 1: gcon = np.array(np.NaN) elif fail == 2: self.userRequestedTermination = True return gcon.copy() - - # Define intermediate callback. If this method returns false, - # Ipopt will terminate with the User_Requested_Stop status. - def eval_intermediate_callback(*args, **kwargs): - if self.userRequestedTermination is True: - return False - else: - return True + def jacobianstructure(_): + return copy.deepcopy(matStruct) + # Define intermediate callback. If this method returns false, + # Ipopt will terminate with the User_Requested_Stop status. + def intermediate(_, *args, **kwargs): + if self.userRequestedTermination is True: + return False + else: + return True timeA = time.time() - nnzj = len(matStruct[0]) - nnzh = 0 - - nlp = pyipoptcore.create( - len(xs), - blx, - bux, - ncon, - blc, - buc, - nnzj, - nnzh, - eval_f, - eval_grad_f, - eval_g, - eval_jac_g, - ) - nlp.set_intermediate_callback(eval_intermediate_callback) + nlp = cyipopt.Problem( + n=len(xs), + m=ncon, + problem_obj=CyIPOPTProblem(), + lb=blx, + ub=bux, + cl=blc, + cu=buc, + ) self._set_ipopt_options(nlp) - x, zl, zu, constraint_multipliers, obj, status = nlp.solve(xs) + x, info = nlp.solve(xs) nlp.close() optTime = time.time() - timeA @@ -292,11 +267,11 @@ def eval_intermediate_callback(*args, **kwargs): # Store Results sol_inform = {} - sol_inform["value"] = status - sol_inform["text"] = self.informs[status] + sol_inform["value"] = info["status"] + sol_inform["text"] = self.informs[info["status"]] # Create the optimization solution - sol = self._createSolution(optTime, sol_inform, obj, x, multipliers=constraint_multipliers) + sol = self._createSolution(optTime, sol_inform, info["obj_val"], x, multipliers=info["mult_g"]) # Indicate solution finished self.optProb.comm.bcast(-1, root=0) @@ -317,11 +292,4 @@ def _set_ipopt_options(self, nlp): # --------------------------------------------- for name, value in self.options.items(): - if isinstance(value, str): - nlp.str_option(name, value) - elif isinstance(value, float): - nlp.num_option(name, value) - elif isinstance(value, int): - nlp.int_option(name, value) - else: - print("invalid option type", type(value)) + nlp.add_option(name, value) From f3b53a46badd859f71b7c80edd41a9ffc193b71a Mon Sep 17 00:00:00 2001 From: Ella Wu <602725+ewu63@users.noreply.github.com> Date: Tue, 11 Mar 2025 23:54:23 -0700 Subject: [PATCH 02/16] clean up meson.build --- meson_options.txt | 3 -- pyoptsparse/meson.build | 2 +- pyoptsparse/pyALPSO/meson.build | 24 ++++++------- pyoptsparse/pyIPOPT/meson.build | 60 +++------------------------------ setup.py | 10 +----- 5 files changed, 19 insertions(+), 80 deletions(-) diff --git a/meson_options.txt b/meson_options.txt index 356ab893c..e41d6cc33 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,6 +1,3 @@ -option('ipopt_dir', type: 'string', value: '', - description: 'Top-level dir for ipopt') - option('incdir_numpy', type: 'string', value: '', description: 'Include directory for numpy. If left empty Meson will try to find it on its own.') diff --git a/pyoptsparse/meson.build b/pyoptsparse/meson.build index 0467ed190..c8394c6c6 100644 --- a/pyoptsparse/meson.build +++ b/pyoptsparse/meson.build @@ -66,7 +66,7 @@ inc_f2py = include_directories(incdir_f2py) #) subdir('pySNOPT') -subdir('pyIPOPT') +# subdir('pyIPOPT') subdir('pySLSQP') subdir('pyCONMIN') subdir('pyNLPQLP') diff --git a/pyoptsparse/pyALPSO/meson.build b/pyoptsparse/pyALPSO/meson.build index b49fafe2d..6c87b5030 100644 --- a/pyoptsparse/pyALPSO/meson.build +++ b/pyoptsparse/pyALPSO/meson.build @@ -1,13 +1,13 @@ -python_sources = [ - '__init__.py', - 'alpso.py', - 'alpso_ext.py', - 'pyALPSO.py', - 'LICENSE' -] +# python_sources = [ +# '__init__.py', +# 'alpso.py', +# 'alpso_ext.py', +# 'pyALPSO.py', +# 'LICENSE' +# ] -py3_target.install_sources( - python_sources, - pure: true, - subdir: 'pyoptsparse/pyALPSO' -) \ No newline at end of file +# py3_target.install_sources( +# python_sources, +# pure: true, +# subdir: 'pyoptsparse/pyALPSO' +# ) \ No newline at end of file diff --git a/pyoptsparse/pyIPOPT/meson.build b/pyoptsparse/pyIPOPT/meson.build index d26259d68..7ea72e616 100644 --- a/pyoptsparse/pyIPOPT/meson.build +++ b/pyoptsparse/pyIPOPT/meson.build @@ -1,60 +1,10 @@ -fs = import('fs') - -if get_option('ipopt_dir') != '' or fs.is_dir('Ipopt') - - ipopt_dir = '' - - if get_option('ipopt_dir') != '' - ipopt_dir = get_option('ipopt_dir') - elif fs.is_dir('Ipopt') - ipopt_dir = fs.is_dir('Ipopt') - endif - - ipopt_lib = [] - ipopt_idir = '' - - # Ipopt installs differently on some systems (i.e. Fedora) - if fs.is_dir(ipopt_dir / 'lib') - ipopt_lib = [ipopt_dir / 'lib'] - elif fs.is_dir(ipopt_dir / 'lib64') - ipopt_lib = [ipopt_dir / 'lib64'] - endif - - - if fs.is_dir(ipopt_dir / 'include' / 'coin-or') - ipopt_idir = ipopt_dir / 'include' / 'coin-or' - elif fs.is_dir(ipopt_dir / 'include' / 'coin') - ipopt_idir = ipopt_dir / 'include' / 'coin' - endif - - ipopt_dep = cc.find_library('ipopt-3', required: false, dirs: ipopt_lib) # only relevant on windows - if not ipopt_dep.found() - ipopt_dep = cc.find_library('ipopt', required: true, dirs: ipopt_lib) - endif - - if fs.is_dir(ipopt_idir) - ipopt_inc = include_directories(ipopt_idir) - else - error('IPOPT include directory not found: ', ipopt_dir) - endif - - py3_target.extension_module('pyipoptcore', - 'src/callback.c', - 'src/pyipoptcoremodule.c', - include_directories: [inc_np, 'src', ipopt_inc], - dependencies : [py3_dep, ipopt_dep], - subdir: 'pyoptsparse/pyIPOPT', - link_language: 'c', - install : false) -endif - -#python_sources = [ +# python_sources = [ # '__init__.py', # 'pyIPOPT.py', -#] -# -#py3_target.install_sources( +# ] + +# py3_target.install_sources( # python_sources, # pure: false, # subdir: 'pyoptsparse/pyIPOPT' -#) +# ) diff --git a/setup.py b/setup.py index 9f068765e..c100711eb 100644 --- a/setup.py +++ b/setup.py @@ -6,11 +6,6 @@ def run_meson_build(): - # check if ipopt dir is specified - ipopt_dir_opt = "" - if "IPOPT_DIR" in os.environ: - ipopt_dir = os.environ["IPOPT_DIR"] - ipopt_dir_opt = f"-Dipopt_dir={ipopt_dir}" prefix = os.path.join(os.getcwd(), staging_dir) purelibdir = "." @@ -18,15 +13,12 @@ def run_meson_build(): meson_args = "" if "MESON_ARGS" in os.environ: meson_args = os.environ["MESON_ARGS"] - # check to make sure ipopt dir isnt specified twice - if "-Dipopt_dir" in meson_args and ipopt_dir_opt != "": - raise RuntimeError("IPOPT_DIR environment variable is set and '-Dipopt_dir' in MESON_ARGS") # configure meson_path = shutil.which("meson") meson_call = ( f"{meson_path} setup {staging_dir} --prefix={prefix} " - + f"-Dpython.purelibdir={purelibdir} -Dpython.platlibdir={purelibdir} {ipopt_dir_opt} {meson_args}" + + f"-Dpython.purelibdir={purelibdir} -Dpython.platlibdir={purelibdir} {meson_args}" ) sysargs = meson_call.split(" ") sysargs = [arg for arg in sysargs if arg != ""] From 5604b953874a7dcf03387f38b5d24e9d71c64d3a Mon Sep 17 00:00:00 2001 From: Ella Wu <602725+ewu63@users.noreply.github.com> Date: Tue, 11 Mar 2025 23:55:44 -0700 Subject: [PATCH 03/16] remove old ipopt bindings --- pyoptsparse/pyIPOPT/src/callback.c | 645 ---------------- pyoptsparse/pyIPOPT/src/hook.h | 71 -- pyoptsparse/pyIPOPT/src/pyipoptcoremodule.c | 781 -------------------- 3 files changed, 1497 deletions(-) delete mode 100644 pyoptsparse/pyIPOPT/src/callback.c delete mode 100644 pyoptsparse/pyIPOPT/src/hook.h delete mode 100644 pyoptsparse/pyIPOPT/src/pyipoptcoremodule.c diff --git a/pyoptsparse/pyIPOPT/src/callback.c b/pyoptsparse/pyIPOPT/src/callback.c deleted file mode 100644 index 89c6a4301..000000000 --- a/pyoptsparse/pyIPOPT/src/callback.c +++ /dev/null @@ -1,645 +0,0 @@ -/* - * Copyright (c) 2008, Eric You Xu, Washington University All rights - * reserved. Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following conditions - * are met: - * - * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. * Redistributions in - * binary form must reproduce the above copyright notice, this list of - * conditions and the following disclaimer in the documentation and/or other - * materials provided with the distribution. * Neither the name of the - * Washington University nor the names of its contributors may be used to - * endorse or promote products derived from this software without specific - * prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -/* - * Added "eval_intermediate_callback" by - * OpenMDAO at NASA Glenn Research Center, 2010 and 2011 - * - * Changed logger from code contributed by alanfalloon -*/ - -#include "hook.h" - -void logger(const char *fmt, ...) -{ - if (user_log_level == VERBOSE) { - va_list ap; - va_start(ap, fmt); - PySys_WriteStdout(fmt, ap); - va_end(ap); - PySys_WriteStdout("\n"); - } -} - -Bool eval_intermediate_callback(Index alg_mod, /* 0 is regular, 1 is resto */ - Index iter_count, Number obj_value, - Number inf_pr, Number inf_du, - Number mu, Number d_norm, - Number regularization_size, - Number alpha_du, Number alpha_pr, - Index ls_trials, UserDataPtr data) -{ - //logger("[Callback:E]intermediate_callback"); - - DispatchData *myowndata = (DispatchData *) data; - UserDataPtr user_data = (UserDataPtr) myowndata->userdata; - - long result_as_long; - Bool result_as_bool; - - PyObject *python_algmod = Py_BuildValue("i", alg_mod); - PyObject *python_iter_count = Py_BuildValue("i", iter_count); - PyObject *python_obj_value = Py_BuildValue("d", obj_value); - PyObject *python_inf_pr = Py_BuildValue("d", inf_pr); - PyObject *python_inf_du = Py_BuildValue("d", inf_du); - PyObject *python_mu = Py_BuildValue("d", mu); - PyObject *python_d_norm = Py_BuildValue("d", d_norm); - PyObject *python_regularization_size = - Py_BuildValue("d", regularization_size); - PyObject *python_alpha_du = Py_BuildValue("d", alpha_du); - PyObject *python_alpha_pr = Py_BuildValue("d", alpha_pr); - PyObject *python_ls_trials = Py_BuildValue("i", ls_trials); - - PyObject *arglist = NULL; - - if (user_data != NULL) - arglist = Py_BuildValue("(OOOOOOOOOOOO)", - python_algmod, - python_iter_count, - python_obj_value, - python_inf_pr, - python_inf_du, - python_mu, - python_d_norm, - python_regularization_size, - python_alpha_du, - python_alpha_pr, - python_ls_trials, - (PyObject *) user_data); - else - arglist = Py_BuildValue("(OOOOOOOOOOO)", - python_algmod, - python_iter_count, - python_obj_value, - python_inf_pr, - python_inf_du, - python_mu, - python_d_norm, - python_regularization_size, - python_alpha_du, - python_alpha_pr, python_ls_trials); - - PyObject *result = - PyObject_CallObject(myowndata->eval_intermediate_callback_python, - arglist); - - if (!result) - PyErr_Print(); - - result_as_long = PyLong_AsLong(result); - result_as_bool = (Bool) result_as_long; - - Py_DECREF(result); - Py_CLEAR(arglist); - //logger("[Callback:R] intermediate_callback"); - return result_as_bool; -} - -Bool -eval_f(Index n, Number * x, Bool new_x, Number * obj_value, UserDataPtr data) -{ - //logger("[Callback:E] eval_f"); - - npy_intp dims[1]; - dims[0] = n; - - DispatchData *myowndata = (DispatchData *) data; - UserDataPtr user_data = (UserDataPtr) myowndata->userdata; - - // import_array (); - - import_array1(FALSE); - PyObject *arrayx = - PyArray_SimpleNewFromData(1, dims, NPY_DOUBLE, (char *)x); - if (!arrayx) - return FALSE; - - if (new_x && myowndata->apply_new_python) { - /* Call the python function to applynew */ - PyObject *arg1; - arg1 = Py_BuildValue("(O)", arrayx); - PyObject *tempresult = PyObject_CallObject( - myowndata->apply_new_python, arg1); - if (tempresult == NULL) { - logger("[Error] Python function apply_new returns NULL"); - PyErr_Print(); - Py_DECREF(arg1); - return FALSE; - } - Py_DECREF(arg1); - Py_DECREF(tempresult); - } - - PyObject *arglist; - if (user_data != NULL) { - arglist = Py_BuildValue("(OO)", arrayx, (PyObject *) user_data); - } else { - arglist = Py_BuildValue("(O)", arrayx); - } - - PyObject *result = PyObject_CallObject(myowndata->eval_f_python, arglist); - - if (result == NULL) { - logger("[Error] Python function eval_f returns NULL"); - PyErr_Print(); - Py_DECREF(arrayx); - Py_CLEAR(arglist); - return FALSE; - } - - *obj_value = PyFloat_AsDouble(result); - - if (PyErr_Occurred()) { - logger("[Error] Python function eval_f returns non-PyFloat"); - PyErr_Print(); - Py_DECREF(result); - Py_DECREF(arrayx); - Py_CLEAR(arglist); - return FALSE; - } - - Py_DECREF(result); - Py_DECREF(arrayx); - Py_CLEAR(arglist); - //logger("[Callback:R] eval_f"); - return TRUE; -} - -Bool -eval_grad_f(Index n, Number * x, Bool new_x, Number * grad_f, UserDataPtr data) -{ - //logger("[Callback:E] eval_grad_f"); - - DispatchData *myowndata = (DispatchData *) data; - UserDataPtr user_data = (UserDataPtr) myowndata->userdata; - - if (myowndata->eval_grad_f_python == NULL) - PyErr_Print(); - - /* int dims[1]; */ - npy_intp dims[1]; - dims[0] = n; - // import_array (); - - import_array1(FALSE); - - /* - * PyObject *arrayx = PyArray_FromDimsAndData(1, dims, NPY_DOUBLE - * , (char*) x); - */ - PyObject *arrayx = - PyArray_SimpleNewFromData(1, dims, NPY_DOUBLE, (char *)x); - if (!arrayx) - return FALSE; - - if (new_x && myowndata->apply_new_python) { - /* Call the python function to applynew */ - PyObject *arg1 = Py_BuildValue("(O)", arrayx); - PyObject *tempresult = PyObject_CallObject( - myowndata->apply_new_python, arg1); - if (tempresult == NULL) { - logger("[Error] Python function apply_new returns NULL"); - PyErr_Print(); - Py_DECREF(arg1); - return FALSE; - } - Py_DECREF(arg1); - Py_DECREF(tempresult); - } - - PyObject *arglist; - if (user_data != NULL) - arglist = Py_BuildValue("(OO)", arrayx, (PyObject *) user_data); - else - arglist = Py_BuildValue("(O)", arrayx); - - PyArrayObject *result = (PyArrayObject *) PyObject_CallObject( - myowndata->eval_grad_f_python, arglist); - - if (result == NULL) { - logger("[Error] Python function eval_grad_f returns NULL"); - PyErr_Print(); - return FALSE; - } - - if (!PyArray_Check(result)) { - logger("[Error] Python function eval_grad_f returns non-PyArray"); - Py_DECREF(result); - return FALSE; - } - - double *tempdata = (double *)result->data; - int i; - for (i = 0; i < n; i++) - grad_f[i] = tempdata[i]; - - Py_DECREF(result); - Py_CLEAR(arrayx); - Py_CLEAR(arglist); - //logger("[Callback:R] eval_grad_f"); - return TRUE; -} - -Bool -eval_g(Index n, Number * x, Bool new_x, Index m, Number * g, UserDataPtr data) -{ - - //logger("[Callback:E] eval_g"); - - DispatchData *myowndata = (DispatchData *) data; - UserDataPtr user_data = (UserDataPtr) myowndata->userdata; - - if (myowndata->eval_g_python == NULL) - PyErr_Print(); - /* int dims[1]; */ - npy_intp dims[1]; - int i; - double *tempdata; - - dims[0] = n; - // import_array (); - - import_array1(FALSE); - - /* - * PyObject *arrayx = PyArray_FromDimsAndData(1, dims, NPY_DOUBLE - * , (char*) x); - */ - PyObject *arrayx = - PyArray_SimpleNewFromData(1, dims, NPY_DOUBLE, (char *)x); - if (!arrayx) - return FALSE; - - if (new_x && myowndata->apply_new_python) { - /* Call the python function to applynew */ - PyObject *arg1 = Py_BuildValue("(O)", arrayx); - PyObject *tempresult = PyObject_CallObject( - myowndata->apply_new_python, arg1); - if (tempresult == NULL) { - logger("[Error] Python function apply_new returns NULL"); - PyErr_Print(); - Py_DECREF(arg1); - return FALSE; - } - Py_DECREF(arg1); - Py_DECREF(tempresult); - } - - PyObject *arglist; - if (user_data != NULL) - arglist = Py_BuildValue("(OO)", arrayx, (PyObject *) user_data); - else - arglist = Py_BuildValue("(O)", arrayx); - - PyArrayObject *result = (PyArrayObject *) PyObject_CallObject( - myowndata->eval_g_python, arglist); - - if (result == NULL) { - logger("[Error] Python function eval_g returns NULL"); - PyErr_Print(); - return FALSE; - } - - if (!PyArray_Check(result)) { - logger("[Error] Python function eval_g returns non-PyArray"); - Py_DECREF(result); - return FALSE; - } - - tempdata = (double *)result->data; - for (i = 0; i < m; i++) { - g[i] = tempdata[i]; - } - - Py_DECREF(result); - Py_CLEAR(arrayx); - Py_CLEAR(arglist); - //logger("[Callback:R] eval_g"); - return TRUE; -} - -Bool -eval_jac_g(Index n, Number * x, Bool new_x, - Index m, Index nele_jac, - Index * iRow, Index * jCol, Number * values, UserDataPtr data) -{ - - //logger("[Callback:E] eval_jac_g"); - - DispatchData *myowndata = (DispatchData *) data; - UserDataPtr user_data = (UserDataPtr) myowndata->userdata; - - int i; - long *rowd = NULL; - long *cold = NULL; - - /* int dims[1]; */ - npy_intp dims[1]; - dims[0] = n; - - double *tempdata; - - if (myowndata->eval_grad_f_python == NULL) /* Why??? */ - PyErr_Print(); - - if (values == NULL) { - /* import_array (); */ - import_array1(FALSE); - - PyObject *arrayx = - PyArray_SimpleNewFromData(1, dims, NPY_DOUBLE, - (char *)x); - if (!arrayx) - return FALSE; - - PyObject *arglist; - - if (user_data != NULL) - arglist = Py_BuildValue("(OOO)", - arrayx, Py_True, - (PyObject *) user_data); - else - arglist = Py_BuildValue("(OO)", arrayx, Py_True); - - PyObject *result = - PyObject_CallObject(myowndata->eval_jac_g_python, arglist); - if (!result) { - - logger("[PyIPOPT] return from eval_jac_g is null\n"); - /* TODO: need to deal with reference counting here */ - return FALSE; - } - if (!PyTuple_Check(result)) { - PyErr_Print(); - } - PyArrayObject *row = - (PyArrayObject *) PyTuple_GetItem(result, 0); - PyArrayObject *col = - (PyArrayObject *) PyTuple_GetItem(result, 1); - - if (!row || !col || !PyArray_Check(row) || !PyArray_Check(col)) { - logger - ("[Error] there are problems with row or col in eval_jac_g.\n"); - PyErr_Print(); - } - rowd = (long *)row->data; - cold = (long *)col->data; - - for (i = 0; i < nele_jac; i++) { - iRow[i] = (Index) rowd[i]; - jCol[i] = (Index) cold[i]; - } - Py_CLEAR(arrayx); - Py_DECREF(result); - Py_CLEAR(arglist); - //logger("[Callback:R] eval_jac_g(1)"); - } else { - PyObject *arrayx = - PyArray_SimpleNewFromData(1, dims, NPY_DOUBLE, - (char *)x); - - if (!arrayx) - return FALSE; - - if (new_x && myowndata->apply_new_python) { - /* Call the python function to applynew */ - PyObject *arg1 = Py_BuildValue("(O)", arrayx); - PyObject *tempresult = - PyObject_CallObject(myowndata->apply_new_python, - arg1); - if (tempresult == NULL) { - logger("[Error] Python function apply_new returns NULL"); - Py_DECREF(arg1); - return FALSE; - } - Py_DECREF(arg1); - Py_DECREF(tempresult); - } - PyObject *arglist; - if (user_data != NULL) - arglist = Py_BuildValue("(OOO)", - arrayx, Py_False, - (PyObject *) user_data); - else - arglist = Py_BuildValue("(OO)", arrayx, Py_False); - - PyArrayObject *result = (PyArrayObject *) PyObject_CallObject( - myowndata->eval_jac_g_python, arglist); - - if (result == NULL) { - logger("[Error] Python function eval_jac_g returns NULL"); - PyErr_Print(); - return FALSE; - } - - if (!PyArray_Check(result)) { - logger("[Error] Python function eval_jac_g returns non-PyArray"); - Py_DECREF(result); - return FALSE; - } - - /* - * Code is buggy here. We assume that result is a double - * array - */ - assert(result->descr->type == 'd'); - tempdata = (double *)result->data; - - for (i = 0; i < nele_jac; i++) - values[i] = tempdata[i]; - - Py_DECREF(result); - Py_CLEAR(arrayx); - Py_CLEAR(arglist); - //logger("[Callback:R] eval_jac_g(2)"); - } - //logger("[Callback:R] eval_jac_g"); - return TRUE; -} - -Bool -eval_h(Index n, Number * x, Bool new_x, Number obj_factor, - Index m, Number * lambda, Bool new_lambda, - Index nele_hess, Index * iRow, Index * jCol, - Number * values, UserDataPtr data) -{ - //logger("[Callback:E] eval_h"); - - DispatchData *myowndata = (DispatchData *) data; - UserDataPtr user_data = (UserDataPtr) myowndata->userdata; - - int i; - npy_intp dims[1]; - npy_intp dims2[1]; - - if (myowndata->eval_h_python == NULL) { - logger("[Error] There is no eval_h assigned"); - return FALSE; - } - if (values == NULL) { - //logger("[Callback:E] eval_h (1a)"); - PyObject *newx = Py_True; - PyObject *objfactor = Py_BuildValue("d", obj_factor); - PyObject *lagrange = Py_True; - - PyObject *arglist; - - if (user_data != NULL) { - arglist = Py_BuildValue( - "(OOOOO)", newx, lagrange, objfactor, Py_True, - (PyObject *) user_data); - } else { - arglist = Py_BuildValue( - "(OOOO)", newx, lagrange, objfactor, Py_True); - } - - if (arglist == NULL) { - logger("[Error] failed to build arglist for eval_h"); - PyErr_Print(); - return FALSE; - } else { - logger("[Logspam] built arglist for eval_h"); - } - - PyObject *result = PyObject_CallObject(myowndata->eval_h_python, arglist); - - if (result == NULL) { - logger("[Error] Python function eval_h returns NULL"); - PyErr_Print(); - return FALSE; - } else { - logger("[Logspam] Python function eval_h returns non-NULL"); - } - - int result_size = PyTuple_Size(result); - - if (result_size == -1) { - logger("[Error] Python function eval_h returns non-PyTuple"); - Py_DECREF(result); - return FALSE; - } - - if (result_size != 2) { - logger("[Error] Python function eval_h returns a tuple whose len != 2"); - Py_DECREF(result); - return FALSE; - } - - //logger("[Callback:E] eval_h (tuple is the right length)"); - - PyArrayObject *row = (PyArrayObject *) PyTuple_GetItem(result, 0); - PyArrayObject *col = (PyArrayObject *) PyTuple_GetItem(result, 1); - - long *rdata = (long *)row->data; - long *cdata = (long *)col->data; - - for (i = 0; i < nele_hess; i++) { - iRow[i] = (Index) rdata[i]; - jCol[i] = (Index) cdata[i]; - /* - * logger("PyIPOPT_DEBUG %d, %d\n", iRow[i], - * jCol[i]); - */ - } - - //logger("[Callback:E] eval_h (clearing stuff now)"); - - Py_DECREF(objfactor); - Py_DECREF(result); - Py_CLEAR(arglist); - //logger("[Callback:R] eval_h (1b)"); - } else { - //logger("[Callback:R] eval_h (2a)"); - - PyObject *objfactor = Py_BuildValue("d", obj_factor); - - dims[0] = n; - PyObject *arrayx = - PyArray_SimpleNewFromData(1, dims, NPY_DOUBLE, - (char *)x); - if (!arrayx) - return FALSE; - - if (new_x && myowndata->apply_new_python) { - /* Call the python function to applynew */ - PyObject *arg1 = Py_BuildValue("(O)", arrayx); - PyObject *tempresult = PyObject_CallObject( - myowndata->apply_new_python, arg1); - if (tempresult == NULL) { - logger("[Error] Python function apply_new returns NULL"); - PyErr_Print(); - Py_DECREF(arg1); - return FALSE; - } - Py_DECREF(arg1); - Py_DECREF(tempresult); - } - dims2[0] = m; - PyObject *lagrangex = PyArray_SimpleNewFromData( - 1, dims2, NPY_DOUBLE, (char *)lambda); - if (!lagrangex) - return FALSE; - - PyObject *arglist; - - if (user_data != NULL) { - arglist = Py_BuildValue( - "(OOOOO)", arrayx, lagrangex, objfactor, Py_False, - (PyObject *) user_data); - } else { - arglist = Py_BuildValue( - "(OOOO)", arrayx, lagrangex, objfactor, Py_False); - } - PyArrayObject *result = (PyArrayObject *) PyObject_CallObject( - myowndata->eval_h_python, arglist); - - if (result == NULL) { - logger("[Error] Python function eval_h returns NULL"); - PyErr_Print(); - return FALSE; - } - - if (!PyArray_Check(result)) { - logger("[Error] Python function eval_h returns non-PyArray"); - Py_DECREF(result); - return FALSE; - } - - double *tempdata = (double *)result->data; - for (i = 0; i < nele_hess; i++) { - values[i] = tempdata[i]; - } - Py_CLEAR(arrayx); - Py_CLEAR(lagrangex); - Py_CLEAR(objfactor); - Py_DECREF(result); - Py_CLEAR(arglist); - //logger("[Callback:R] eval_h (2b)"); - } - return TRUE; -} diff --git a/pyoptsparse/pyIPOPT/src/hook.h b/pyoptsparse/pyIPOPT/src/hook.h deleted file mode 100644 index 54058e41e..000000000 --- a/pyoptsparse/pyIPOPT/src/hook.h +++ /dev/null @@ -1,71 +0,0 @@ -// Author: Eric Xu -// Licensed under BSD - -#include "Python.h" -#include "IpStdCInterface.h" -#include -#include "numpy/arrayobject.h" - -#ifndef PY_IPOPT_HOOK_ -#define PY_IPOPT_HOOK_ - -// A series of callback functions used by Ipopt C Interface -Bool eval_f(Index n, - Number * x, Bool new_x, Number * obj_value, UserDataPtr user_data); - -Bool eval_grad_f(Index n, - Number * x, - Bool new_x, Number * grad_f, UserDataPtr user_data); - -Bool eval_g(Index n, - Number * x, Bool new_x, Index m, Number * g, UserDataPtr user_data); - -Bool eval_jac_g(Index n, Number * x, Bool new_x, - Index m, Index nele_jac, - Index * iRow, Index * jCol, Number * values, - UserDataPtr user_data); - -Bool eval_h(Index n, Number * x, Bool new_x, Number obj_factor, - Index m, Number * lambda, Bool new_lambda, - Index nele_hess, Index * iRow, Index * jCol, - Number * values, UserDataPtr user_data); - -Bool eval_intermediate_callback(Index alg_mod, - Index iter_count, Number obj_value, - Number inf_pr, Number inf_du, - Number mu, Number d_norm, - Number regularization_size, - Number alpha_du, Number alpha_pr, - Index ls_trials, UserDataPtr data); - -typedef struct { - PyObject *eval_f_python; - PyObject *eval_grad_f_python; - PyObject *eval_g_python; - PyObject *eval_jac_g_python; - PyObject *eval_h_python; - PyObject *apply_new_python; - PyObject *eval_intermediate_callback_python; - PyObject *userdata; -} DispatchData; - - -#if PY_MAJOR_VERSION < 3 -PyObject *problem_getattr(PyObject * self, char *attrname); -#endif - -/* Logging */ -#define VERBOSE 2 -#define IPOPT_OUTPUT 1 -#define TERSE 0 -extern int user_log_level; -void logger(const char *fmt, ...); - -typedef struct { - PyObject_HEAD IpoptProblem nlp; - DispatchData *data; - Index n_variables; - Index m_constraints; -} problem; - -#endif // PY_IPOPT_HOOK_ diff --git a/pyoptsparse/pyIPOPT/src/pyipoptcoremodule.c b/pyoptsparse/pyIPOPT/src/pyipoptcoremodule.c deleted file mode 100644 index 636e63b31..000000000 --- a/pyoptsparse/pyIPOPT/src/pyipoptcoremodule.c +++ /dev/null @@ -1,781 +0,0 @@ -/* Author: Eric Xu */ -/* Licensed under BSD */ -/* */ -/* Modifications on logger made by */ -/* OpenMDAO at NASA Glenn Research Center, 2010 and 2011 */ -/* Modifications on the SAFE_FREE macro made by */ -/* Guillaume Jacquenot, 2012 */ - -#include "hook.h" - -#ifndef SAFE_FREE -#define SAFE_FREE(p) {if (p) {free(p); (p)= NULL;}} -#endif - -/* - * Let's put the static char docs at the beginning of this file... - */ - -static char PYIPOPT_SOLVE_DOC[] = "solve(x) -> (x, ml, mu, obj)\n \ - \n \ - Call Ipopt to solve problem created before and return \n \ - a tuple that contains final solution x, upper and lower\n \ - bound for multiplier, final objective function obj, \n \ - and the return status of ipopt. \n"; - -static char PYIPOPT_SET_INTERMEDIATE_CALLBACK_DOC[] = - "set_intermediate_callback(callback_function)\n \ - \n \ - Set the intermediate callback function. \ - This gets called each iteration."; - -static char PYIPOPT_CLOSE_DOC[] = "After all the solving, close the model\n"; - -static char PYIPOPT_ADD_STR_OPTION_DOC[] = - "Set the String (char* in C) option for Ipopt. Refer to the Ipopt \n \ - document for more information about Ipopt options, or use \n \ - ipopt --print-options \n \ - to see a list of available options."; - -static char PYIPOPT_ADD_INT_OPTION_DOC[] = - "Set the Int (int in C) option for Ipopt. Refer to the Ipopt \n \ - document for more information about Ipopt options, or use \n \ - ipopt --print-options \n \ - to see a list of available options."; - -static char PYIPOPT_ADD_NUM_OPTION_DOC[] = - "Set the Number (double in C) option for Ipopt. Refer to the Ipopt \n \ - document for more information about Ipopt options, or use \n \ - ipopt --print-options \n \ - to see a list of available options."; - -static char PYIPOPT_CREATE_DOC[] = - "create(n, xl, xu, m, gl, gu, nnzj, nnzh, eval_f, eval_grad_f, eval_g, eval_jac_g) -> Boolean\n \ - \n \ - Create a problem instance and return True if succeed \n \ - \n \ - n is the number of variables, \n \ - xl is the lower bound of x as bounded constraints \n \ - xu is the upper bound of x as bounded constraints \n \ - both xl, xu should be one dimension arrays with length n \n \ - \n \ - m is the number of constraints, \n \ - gl is the lower bound of constraints \n \ - gu is the upper bound of constraints \n \ - both gl, gu should be one dimension arrays with length m \n \ - nnzj is the number of nonzeros in Jacobi matrix \n \ - nnzh is the number of non-zeros in Hessian matrix, you can set it to 0 \n \ - \n \ - eval_f is the call back function to calculate objective value, \n \ - it takes one single argument x as input vector \n \ - eval_grad_f calculates gradient for objective function \n \ - eval_g calculates the constraint values and return an array \n \ - eval_jac_g calculates the Jacobi matrix. It takes two arguments, \n \ - the first is the variable x and the second is a Boolean flag \n \ - if the flag is true, it supposed to return a tuple (row, col) \n \ - to indicate the sparse Jacobi matrix's structure. \n \ - if the flag is false if returns the values of the Jacobi matrix \n \ - with length nnzj \n \ - eval_h calculates the hessian matrix, it's optional. \n \ - if omitted, please set nnzh to 0 and Ipopt will use approximated hessian \n \ - which will make the convergence slower. "; - -static char PYIPOPT_LOG_DOC[] = "set_loglevel(level)\n \ - \n \ - Set the log level of PyIPOPT \n \ - levels: \n \ - 0: Terse, no log from pyipopt \n \ - 1: Moderate, logs for ipopt \n \ - 2: Verbose, logs for both ipopt and pyipopt. \n"; - - - -int user_log_level = TERSE; - -/* Object Section */ -/* sig of this is void foo(PyO*) */ -static void problem_dealloc(PyObject * self) -{ - problem *temp = (problem *) self; - SAFE_FREE(temp->data); - Py_TYPE(self)->tp_free((PyObject*)self); -} - -PyObject *solve(PyObject * self, PyObject * args); -PyObject *set_intermediate_callback(PyObject * self, PyObject * args); -PyObject *close_model(PyObject * self, PyObject * args); - -static PyObject *add_str_option(PyObject * self, PyObject * args) -{ - problem *temp = (problem *) self; - IpoptProblem nlp = (IpoptProblem) (temp->nlp); - char *param; - char *value; - Bool ret; - - if (!PyArg_ParseTuple(args, "ss:str_option", ¶m, &value)) { - return NULL; - } - ret = AddIpoptStrOption(nlp, (char *)param, value); - if (ret) { - Py_INCREF(Py_True); - return Py_True; - } else { - return PyErr_Format(PyExc_ValueError, - "%s is not a valid string option", param); - } -} - -static PyObject *add_int_option(PyObject * self, PyObject * args) -{ - - problem *temp = (problem *) self; - IpoptProblem nlp = (IpoptProblem) (temp->nlp); - - char *param; - int value; - - Bool ret; - - if (!PyArg_ParseTuple(args, "si:int_option", ¶m, &value)) { - return NULL; - } - ret = AddIpoptIntOption(nlp, (char *)param, value); - if (ret) { - Py_INCREF(Py_True); - return Py_True; - } else { - return PyErr_Format(PyExc_ValueError, - "%s is not a valid int option", param); - } -} - -static PyObject *add_num_option(PyObject * self, PyObject * args) -{ - problem *temp = (problem *) self; - IpoptProblem nlp = (IpoptProblem) (temp->nlp); - - char *param; - double value; - - Bool ret; - - if (!PyArg_ParseTuple(args, "sd:num_option", ¶m, &value)) { - return NULL; - } - ret = AddIpoptNumOption(nlp, (char *)param, value); - if (ret) { - Py_INCREF(Py_True); - return Py_True; - } else { - return PyErr_Format(PyExc_ValueError, - "%s is not a valid num option", param); - } -} - -PyMethodDef problem_methods[] = { - {"solve", solve, METH_VARARGS, PYIPOPT_SOLVE_DOC} - , - {"set_intermediate_callback", set_intermediate_callback, METH_VARARGS, - PYIPOPT_SET_INTERMEDIATE_CALLBACK_DOC} - , - {"close", close_model, METH_VARARGS, PYIPOPT_CLOSE_DOC} - , - {"int_option", add_int_option, METH_VARARGS, PYIPOPT_ADD_INT_OPTION_DOC} - , - {"str_option", add_str_option, METH_VARARGS, PYIPOPT_ADD_STR_OPTION_DOC} - , - {"num_option", add_num_option, METH_VARARGS, PYIPOPT_ADD_NUM_OPTION_DOC} - , - {NULL, NULL} - , -}; - -#if PY_MAJOR_VERSION < 3 -PyObject *problem_getattr(PyObject * self, char *attrname) -{ - PyObject *result = NULL; - result = Py_FindMethod(problem_methods, self, attrname); - return result; -} - - -/* - * had to replace PyObject_HEAD_INIT(&PyType_Type) in order to get this to - * compile on Windows - */ -PyTypeObject IpoptProblemType = { - PyObject_HEAD_INIT(NULL) - 0, /* ob_size */ - "pyipoptcore.Problem", /* tp_name */ - sizeof(problem), /* tp_basicsize */ - 0, /* tp_itemsize */ - problem_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - problem_getattr, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT, /* tp_flags */ - "The IPOPT problem object in python", /* tp_doc */ -}; - -#else - -PyDoc_STRVAR(IpoptProblemType__doc__, "The IPOPT problem object in python"); - -PyTypeObject IpoptProblemType = { - PyVarObject_HEAD_INIT(NULL, 0) - "pyipoptcore.Problem", /* tp_name */ - sizeof(problem), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - /* methods */ - (destructor)problem_dealloc, /*tp_dealloc*/ - (printfunc)0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_reserved*/ - (reprfunc)0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - (hashfunc)0, /*tp_hash*/ - (ternaryfunc)0, /*tp_call*/ - (reprfunc)0, /*tp_str*/ - (getattrofunc)0, /* tp_getattro */ - (setattrofunc)0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - IpoptProblemType__doc__, /* tp_doc - Documentation string */ - (traverseproc)0, /* tp_traverse */ - (inquiry)0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - problem_methods, /* tp_methods */ -}; -#endif - -/* - * FIXME: use module or package constants for the log levels, - * either in pyipoptcore or in the parent package. - * They are currently #defined in a header file. - */ -static PyObject *set_loglevel(PyObject * obj, PyObject * args) -{ - int l; - if (!PyArg_ParseTuple(args, "i", &l)) { - PySys_WriteStdout("l is %d \n", l); - return NULL; - } - if (l < 0 || l > 2) { - return NULL; - } - user_log_level = l; - Py_INCREF(Py_True); - return Py_True; -} - -static PyObject *create(PyObject * obj, PyObject * args) -{ - PyObject *f = NULL; - PyObject *gradf = NULL; - PyObject *g = NULL; - PyObject *jacg = NULL; - PyObject *h = NULL; - PyObject *applynew = NULL; - - DispatchData myowndata; - - /* - * I have to create a new python object here, return this python object - */ - - int n; /* Number of variables */ - PyArrayObject *xL = NULL; - PyArrayObject *xU = NULL; - int m; /* Number of constraints */ - PyArrayObject *gL = NULL; - PyArrayObject *gU = NULL; - - problem *object = NULL; - - int nele_jac; - int nele_hess; - - Number *x_L = NULL; /* lower bounds on x */ - Number *x_U = NULL; /* upper bounds on x */ - Number *g_L = NULL; /* lower bounds on g */ - Number *g_U = NULL; /* upper bounds on g */ - - double *xldata, *xudata; - double *gldata, *gudata; - - int i; - - DispatchData *dp = NULL; - - PyObject *retval = NULL; - - /* Init the myowndata field */ - myowndata.eval_f_python = NULL; - myowndata.eval_grad_f_python = NULL; - myowndata.eval_g_python = NULL; - myowndata.eval_jac_g_python = NULL; - myowndata.eval_h_python = NULL; - myowndata.apply_new_python = NULL; - myowndata.userdata = NULL; - - /* "O!", &PyArray_Type &a_x */ - if (!PyArg_ParseTuple(args, "iO!O!iO!O!iiOOOO|OO:pyipoptcreate", - &n, &PyArray_Type, &xL, - &PyArray_Type, &xU, - &m, - &PyArray_Type, &gL, - &PyArray_Type, &gU, - &nele_jac, &nele_hess, - &f, &gradf, &g, &jacg, &h, &applynew)) { - retval = NULL; - SAFE_FREE(x_L); - SAFE_FREE(x_U); - SAFE_FREE(g_L); - SAFE_FREE(g_U); - return retval; - } - if (!PyCallable_Check(f) || - !PyCallable_Check(gradf) || - !PyCallable_Check(g) || !PyCallable_Check(jacg)) { - PyErr_SetString(PyExc_TypeError, - "Need a callable object for callback functions"); - retval = NULL; - SAFE_FREE(x_L); - SAFE_FREE(x_U); - SAFE_FREE(g_L); - SAFE_FREE(g_U); - return retval; - } - myowndata.eval_f_python = f; - myowndata.eval_grad_f_python = gradf; - myowndata.eval_g_python = g; - myowndata.eval_jac_g_python = jacg; - - if (h != NULL) { - if (PyCallable_Check(h)) { - myowndata.eval_h_python = h; - } else { - PyErr_SetString(PyExc_TypeError, - "Need a callable object for function h."); - retval = NULL; - SAFE_FREE(x_L); - SAFE_FREE(x_U); - SAFE_FREE(g_L); - SAFE_FREE(g_U); - return retval; - } - } else { - logger("[PyIPOPT] Ipopt will use Hessian approximation.\n"); - } - - if (applynew != NULL) { - if (PyCallable_Check(applynew)) { - myowndata.apply_new_python = applynew; - } else { - PyErr_SetString(PyExc_TypeError, - "Need a callable object for function applynew."); - retval = NULL; - SAFE_FREE(x_L); - SAFE_FREE(x_U); - SAFE_FREE(g_L); - SAFE_FREE(g_U); - return retval; - } - } - if (m < 0 || n < 0) { - PyErr_SetString(PyExc_TypeError, "m or n can't be negative"); - retval = NULL; - SAFE_FREE(x_L); - SAFE_FREE(x_U); - SAFE_FREE(g_L); - SAFE_FREE(g_U); - return retval; - } - x_L = (Number *) malloc(sizeof(Number) * n); - x_U = (Number *) malloc(sizeof(Number) * n); - if (!x_L || !x_U) { - retval = PyErr_NoMemory(); - SAFE_FREE(x_L); - SAFE_FREE(x_U); - SAFE_FREE(g_L); - SAFE_FREE(g_U); - return retval; - } - xldata = (double *)xL->data; - xudata = (double *)xU->data; - for (i = 0; i < n; i++) { - x_L[i] = xldata[i]; - x_U[i] = xudata[i]; - } - - g_L = (Number *) malloc(sizeof(Number) * m); - g_U = (Number *) malloc(sizeof(Number) * m); - if (!g_L || !g_U) - PyErr_NoMemory(); - - gldata = (double *)gL->data; - gudata = (double *)gU->data; - - for (i = 0; i < m; i++) { - g_L[i] = gldata[i]; - g_U[i] = gudata[i]; - } - - /* Grab the callback objects because we want to use them later. */ - Py_XINCREF(f); - Py_XINCREF(gradf); - Py_XINCREF(g); - Py_XINCREF(jacg); - Py_XINCREF(h); - Py_XINCREF(applynew); - - /* create the Ipopt Problem */ - - int C_indexstyle = 0; - IpoptProblem thisnlp = CreateIpoptProblem(n, - x_L, x_U, m, g_L, g_U, - nele_jac, nele_hess, - C_indexstyle, - &eval_f, &eval_g, - &eval_grad_f, - &eval_jac_g, &eval_h); - logger("[PyIPOPT] Problem created"); - if (!thisnlp) { - PyErr_SetString(PyExc_MemoryError, "Cannot create IpoptProblem instance"); - retval = NULL; - SAFE_FREE(x_L); - SAFE_FREE(x_U); - SAFE_FREE(g_L); - SAFE_FREE(g_U); - return retval; - } - object = PyObject_NEW(problem, &IpoptProblemType); - - if (object != NULL) { - object->n_variables = n; - object->m_constraints = m; - object->nlp = thisnlp; - dp = (DispatchData *) malloc(sizeof(DispatchData)); - if (!dp) { - retval = PyErr_NoMemory(); - SAFE_FREE(x_L); - SAFE_FREE(x_U); - SAFE_FREE(g_L); - SAFE_FREE(g_U); - return retval; - } - memcpy((void *)dp, (void *)&myowndata, sizeof(DispatchData)); - object->data = dp; - retval = (PyObject *) object; - SAFE_FREE(x_L); - SAFE_FREE(x_U); - SAFE_FREE(g_L); - SAFE_FREE(g_U); - return retval; - } else { - PyErr_SetString(PyExc_MemoryError, "Can't create a new Problem instance"); - retval = NULL; - SAFE_FREE(x_L); - SAFE_FREE(x_U); - SAFE_FREE(g_L); - SAFE_FREE(g_U); - return retval; - } -} - -PyObject *set_intermediate_callback(PyObject * self, PyObject * args) -{ - PyObject *intermediate_callback; - problem *temp = (problem *) self; - IpoptProblem nlp = (IpoptProblem) (temp->nlp); - DispatchData myowndata; - DispatchData *bigfield = (DispatchData *) (temp->data); - - /* Init the myowndata field */ - myowndata.eval_intermediate_callback_python = NULL; - - if (!PyArg_ParseTuple(args, "O", &intermediate_callback)) { - return NULL; - } - if (!PyCallable_Check(intermediate_callback)) { - PyErr_SetString(PyExc_TypeError, - "Need a callable object for function!"); - return NULL; - } else { - - bigfield->eval_intermediate_callback_python = - intermediate_callback; - - /* Put a Python function object into this data structure */ - /* - * myowndata.eval_intermediate_callback_python = - * intermediate_callback; - */ - - /* DispatchData *dp = malloc(sizeof(DispatchData)); */ - /* - * memcpy((void*)dp, (void*)&myowndata, - * sizeof(DispatchData)); - */ - /* bigfield = dp; */ - /* - * logger( "qqq: inside set_intermediate_callback, bigfield - * is %p\n", bigfield ) ; - */ - /* - * logger("[PyIPOPT] User specified data field to callback - * function.\n"); - */ - - SetIntermediateCallback(nlp, eval_intermediate_callback); - Py_INCREF(Py_True); - return Py_True; - } -} - -PyObject *solve(PyObject * self, PyObject * args) -{ - enum ApplicationReturnStatus status; /* Solve return code */ - int i; - int n; - - /* Return values */ - problem *temp = (problem *) self; - - IpoptProblem nlp = (IpoptProblem) (temp->nlp); - DispatchData *bigfield = (DispatchData *) (temp->data); - int m = temp->m_constraints; - - /* int dX[1]; */ - npy_intp dX[1]; - npy_intp dlambda[1]; - - PyArrayObject *x = NULL, *mL = NULL, *mU = NULL, *lambda = NULL; - Number obj; /* objective value */ - - PyObject *retval = NULL; - PyArrayObject *x0 = NULL; - - PyObject *myuserdata = NULL; - - Number *newx0 = NULL; - - if (!PyArg_ParseTuple(args, "O!|O", &PyArray_Type, &x0, &myuserdata)) { - retval = NULL; - /* clean up and return */ - if (retval == NULL) { - Py_XDECREF(x); - Py_XDECREF(mL); - Py_XDECREF(mU); - Py_XDECREF(lambda); - } - SAFE_FREE(newx0); - return retval; - } - if (x0->nd != 1){ //If x0 is not 1-dimensional then solve will fail and cause a segmentation fault. - logger("[ERROR] x0 must be a 1-dimensional array"); - Py_XDECREF(x); - Py_XDECREF(mL); - Py_XDECREF(mU); - Py_XDECREF(lambda); - PyErr_SetString(PyExc_TypeError, - "x0 passed to solve is not 1-dimensional."); - return NULL; - } - - if (myuserdata != NULL) { - bigfield->userdata = myuserdata; - /* - * logger("[PyIPOPT] User specified data field to callback - * function.\n"); - */ - } - if (nlp == NULL) { - PyErr_SetString(PyExc_TypeError, - "nlp objective passed to solve is NULL\n Problem created?\n"); - retval = NULL; - /* clean up and return */ - if (retval == NULL) { - Py_XDECREF(x); - Py_XDECREF(mL); - Py_XDECREF(mU); - Py_XDECREF(lambda); - } - SAFE_FREE(newx0); - return retval; - } - if (bigfield->eval_h_python == NULL) { - AddIpoptStrOption(nlp, "hessian_approximation", "limited-memory"); - /* logger("Can't find eval_h callback function\n"); */ - } - /* allocate space for the initial point and set the values */ - npy_intp *dim = ((PyArrayObject *) x0)->dimensions; - n = dim[0]; - dX[0] = n; - - x = (PyArrayObject *) PyArray_SimpleNew(1, dX, NPY_DOUBLE); - if (!x) { - retval = PyErr_NoMemory(); - /* clean up and return */ - if (retval == NULL) { - Py_XDECREF(x); - Py_XDECREF(mL); - Py_XDECREF(mU); - Py_XDECREF(lambda); - } - SAFE_FREE(newx0); - return retval; - } - newx0 = (Number *) malloc(sizeof(Number) * n); - if (!newx0) { - retval = PyErr_NoMemory(); - /* clean up and return */ - if (retval == NULL) { - Py_XDECREF(x); - Py_XDECREF(mL); - Py_XDECREF(mU); - Py_XDECREF(lambda); - } - SAFE_FREE(newx0); - return retval; - } - double *xdata = (double *)x0->data; - for (i = 0; i < n; i++) - newx0[i] = xdata[i]; - - /* Allocate multiplier arrays */ - - mL = (PyArrayObject *) PyArray_SimpleNew(1, dX, NPY_DOUBLE); - mU = (PyArrayObject *) PyArray_SimpleNew(1, dX, NPY_DOUBLE); - dlambda[0] = m; - lambda = (PyArrayObject *) PyArray_SimpleNew(1, dlambda, - NPY_DOUBLE); - - /* For status code, see IpReturnCodes_inc.h in Ipopt */ - - status = - IpoptSolve(nlp, newx0, NULL, &obj, (double *)lambda->data, - (double *)mL->data, (double *)mU->data, - (UserDataPtr) bigfield); - double *return_x_data = (double *)x->data; - for (i = 0; i < n; i++) { - return_x_data[i] = newx0[i]; - } - retval = Py_BuildValue("OOOOdi", - PyArray_Return(x), - PyArray_Return(mL), - PyArray_Return(mU), - PyArray_Return(lambda), - obj, status - ); - /* clean up and return */ - - Py_XDECREF(x); - Py_XDECREF(mL); - Py_XDECREF(mU); - Py_XDECREF(lambda); - - SAFE_FREE(newx0); - return retval; -} - -PyObject *close_model(PyObject * self, PyObject * args) -{ - problem *obj = (problem *) self; - DispatchData *dp = obj->data; - - /* Ungrab the callback functions because we do not need them anymore. */ - Py_XDECREF(dp->eval_f_python); - Py_XDECREF(dp->eval_grad_f_python); - Py_XDECREF(dp->eval_g_python); - Py_XDECREF(dp->eval_jac_g_python); - Py_XDECREF(dp->eval_h_python); - Py_XDECREF(dp->apply_new_python); - - FreeIpoptProblem(obj->nlp); - obj->nlp = NULL; - Py_INCREF(Py_True); - return Py_True; -} - -/* static char PYTEST[] = "TestCreate\n"; */ - -/* static PyObject *test(PyObject *self, PyObject *args) */ -/* { */ -/* IpoptProblem thisnlp = NULL; */ -/* problem *object = NULL; */ -/* object = PyObject_NEW(problem , &IpoptProblemType); */ -/* if (object != NULL) */ -/* object->nlp = thisnlp; */ -/* /\* problem *object = problem_new(thisnlp); *\/ */ -/* return (PyObject *)object; */ -/* } */ - -/* Begin Python Module code section */ -static PyMethodDef ipoptMethods[] = { - /* { "solve", solve, METH_VARARGS, PYIPOPT_SOLVE_DOC}, */ - {"create", create, METH_VARARGS, PYIPOPT_CREATE_DOC}, - /* { "close", close_model, METH_VARARGS, PYIPOPT_CLOSE_DOC}, */ - /* { "test", test, METH_VARARGS, PYTEST}, */ - {"set_loglevel", set_loglevel, METH_VARARGS, PYIPOPT_LOG_DOC}, - {NULL, NULL} -}; - -#if PY_MAJOR_VERSION >= 3 - #define MOD_ERROR_VAL NULL - #define MOD_SUCCESS_VAL(val) val - #define MOD_INIT(name) PyMODINIT_FUNC PyInit_##name(void) - #define MOD_DEF(ob, name, doc, methods) \ - static struct PyModuleDef moduledef = { \ - PyModuleDef_HEAD_INIT, name, doc, -1, methods, }; \ - ob = PyModule_Create(&moduledef); -#else - #define MOD_ERROR_VAL - #define MOD_SUCCESS_VAL(val) - #define MOD_INIT(name) void init##name(void) - #define MOD_DEF(ob, name, doc, methods) \ - ob = Py_InitModule3(name, methods, doc); -#endif - -MOD_INIT(pyipoptcore) -{ - PyObject * m; - /* Finish initialization of the problem type */ - if (PyType_Ready(&IpoptProblemType) < 0) { - return MOD_ERROR_VAL; - } - - MOD_DEF(m, "pyipoptcore", "A hook between Ipopt and Python", ipoptMethods) - - if (m == NULL) - return MOD_ERROR_VAL; - - /* Initialize numpy. */ - /* A segfault will occur if I use numarray without this.. */ - import_array(); - if (PyErr_Occurred()) { - Py_FatalError("Unable to initialize module pyipoptcore"); - } - - return MOD_SUCCESS_VAL(m); -} - -/* End Python Module code section */ From 670648b8606903022030dac4bdec65147ef5f705 Mon Sep 17 00:00:00 2001 From: Ella Wu <602725+ewu63@users.noreply.github.com> Date: Wed, 12 Mar 2025 00:03:33 -0700 Subject: [PATCH 04/16] fix unsized array --- pyoptsparse/pyIPOPT/pyIPOPT.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyoptsparse/pyIPOPT/pyIPOPT.py b/pyoptsparse/pyIPOPT/pyIPOPT.py index acd7b7a90..c9166ffa4 100644 --- a/pyoptsparse/pyIPOPT/pyIPOPT.py +++ b/pyoptsparse/pyIPOPT/pyIPOPT.py @@ -182,8 +182,8 @@ def __call__( jac = extractRows(jac, indices) # Does reordering scaleRows(jac, fact) # Perform logical scaling else: - blc = np.array(-INFINITY) - buc = np.array(INFINITY) + blc = np.atleast_1d(-INFINITY) + buc = np.atleast_1d(INFINITY) ncon = 1 jac = convertToCOO(jac) # Conver to coo format for IPOPT From f2d94693472cc9f836d734ee22510369e0963734 Mon Sep 17 00:00:00 2001 From: Ella Wu <602725+ewu63@users.noreply.github.com> Date: Wed, 12 Mar 2025 00:08:23 -0700 Subject: [PATCH 05/16] make cyipopt optional --- pyoptsparse/pyIPOPT/pyIPOPT.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pyoptsparse/pyIPOPT/pyIPOPT.py b/pyoptsparse/pyIPOPT/pyIPOPT.py index c9166ffa4..8e5eaf82b 100644 --- a/pyoptsparse/pyIPOPT/pyIPOPT.py +++ b/pyoptsparse/pyIPOPT/pyIPOPT.py @@ -8,9 +8,14 @@ import time # External modules -import cyipopt import numpy as np +try: + # External modules + import cyipopt +except ImportError: + cyipopt = None + # Local modules from ..pyOpt_optimizer import Optimizer from ..pyOpt_utils import ICOL, INFINITY, IROW, convertToCOO, extractRows, scaleRows @@ -30,6 +35,8 @@ def __init__(self, raiseError=True, options={}): category = "Local Optimizer" defOpts = self._getDefaultOptions() informs = self._getInforms() + if cyipopt is None and raiseError: + raise ImportError("Could not import cyipopt") super().__init__( name, From 0791cddc0e7a96ff2b760c0850239b6826473c62 Mon Sep 17 00:00:00 2001 From: Ella Wu <602725+ewu63@users.noreply.github.com> Date: Wed, 12 Mar 2025 00:11:34 -0700 Subject: [PATCH 06/16] update build system --- .github/environment.yml | 2 +- .github/workflows/windows-build.yml | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/environment.yml b/.github/environment.yml index b23fb74a7..bd57cd368 100644 --- a/.github/environment.yml +++ b/.github/environment.yml @@ -2,7 +2,6 @@ dependencies: # build - python >=3.9 - numpy >=2.0 - - ipopt - swig - meson >=1.3.2 - compilers @@ -17,6 +16,7 @@ dependencies: - mdolab-baseclasses >=1.3.1 - scipy >=1.7 - sqlitedict >=1.6 + - cyipopt # testing - parameterized - testflo diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index 3d4ef09c9..2a69b189c 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -15,7 +15,7 @@ jobs: strategy: fail-fast: false matrix: - numpy_version: ["1.21.6", "1.25.2"] + numpy_version: ["1.21.6", "1.25.2", "2.1.3"] steps: - uses: actions/checkout@v4 - uses: conda-incubator/setup-miniconda@v3 diff --git a/setup.py b/setup.py index c100711eb..d2e6863a5 100644 --- a/setup.py +++ b/setup.py @@ -105,7 +105,7 @@ def copy_shared_libraries(): "matplotlib", ], "docs": docs_require, - "testing": ["testflo>=1.4.5", "parameterized"], + "testing": ["testflo>=1.4.5", "parameterized", "cyipopt"], }, classifiers=[ "Development Status :: 5 - Production/Stable", From 6a7c907166180d9e7e23843ab8216ee1b5d4fbc3 Mon Sep 17 00:00:00 2001 From: Ella Wu <602725+ewu63@users.noreply.github.com> Date: Wed, 12 Mar 2025 00:33:48 -0700 Subject: [PATCH 07/16] set PKG_CONFIG_PATH --- .github/build_real.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/build_real.sh b/.github/build_real.sh index 0482dcc30..f8b30d862 100755 --- a/.github/build_real.sh +++ b/.github/build_real.sh @@ -11,6 +11,9 @@ fi mv ~/.config/pip/constraints.txt ~/.config/pip/constraints.txt.bkup touch ~/.config/pip/constraints.txt +# set $PKG_CONFIG_PATH so pkg-config can find ipopt +export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$IPOPT_DIR/lib/pkgconfig + pip install .[optview,testing] -v # move pip constraints file back From 5c3f069b4d49a96f29aeffe3177c664c86e3b278 Mon Sep 17 00:00:00 2001 From: Ella Wu <602725+ewu63@users.noreply.github.com> Date: Wed, 12 Mar 2025 00:37:03 -0700 Subject: [PATCH 08/16] format --- pyoptsparse/pyIPOPT/pyIPOPT.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyoptsparse/pyIPOPT/pyIPOPT.py b/pyoptsparse/pyIPOPT/pyIPOPT.py index 8e5eaf82b..3278f1a7c 100644 --- a/pyoptsparse/pyIPOPT/pyIPOPT.py +++ b/pyoptsparse/pyIPOPT/pyIPOPT.py @@ -35,7 +35,7 @@ def __init__(self, raiseError=True, options={}): category = "Local Optimizer" defOpts = self._getDefaultOptions() informs = self._getInforms() - if cyipopt is None and raiseError: + if cyipopt is None and raiseError: raise ImportError("Could not import cyipopt") super().__init__( @@ -239,8 +239,10 @@ def jacobian(_, x): elif fail == 2: self.userRequestedTermination = True return gcon.copy() + def jacobianstructure(_): return copy.deepcopy(matStruct) + # Define intermediate callback. If this method returns false, # Ipopt will terminate with the User_Requested_Stop status. def intermediate(_, *args, **kwargs): From 0dfe38065eb9f34a4044f5ed42d4f585ba450283 Mon Sep 17 00:00:00 2001 From: Ella Wu <602725+ewu63@users.noreply.github.com> Date: Tue, 1 Apr 2025 22:02:59 -0700 Subject: [PATCH 09/16] get rid of IPOPT_DIR --- .github/build_real.sh | 3 --- .github/windows.yaml | 1 - .github/workflows/windows-build.yml | 2 -- 3 files changed, 6 deletions(-) diff --git a/.github/build_real.sh b/.github/build_real.sh index f8b30d862..0482dcc30 100755 --- a/.github/build_real.sh +++ b/.github/build_real.sh @@ -11,9 +11,6 @@ fi mv ~/.config/pip/constraints.txt ~/.config/pip/constraints.txt.bkup touch ~/.config/pip/constraints.txt -# set $PKG_CONFIG_PATH so pkg-config can find ipopt -export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$IPOPT_DIR/lib/pkgconfig - pip install .[optview,testing] -v # move pip constraints file back diff --git a/.github/windows.yaml b/.github/windows.yaml index 4f600ccf3..5255487ec 100644 --- a/.github/windows.yaml +++ b/.github/windows.yaml @@ -27,7 +27,6 @@ jobs: displayName: Install mamba and update environment - script: | - set IPOPT_DIR=%CONDA_PREFIX%\Library set CC=cl set FC=flang set CC_LD=link diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index 2a69b189c..440fee202 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -39,8 +39,6 @@ jobs: :: set fortran compiler, flang 5 activation doesn't seem to do it set FC=flang.exe - set MESON_ARGS=-Dipopt_dir=%CONDA_PREFIX%\Library\ - python -m build -n -x . pip install --no-deps --no-index --find-links dist pyoptsparse From 18f8fdd1e3995813dd567e49099f6e547a52c416 Mon Sep 17 00:00:00 2001 From: Ella Wu <602725+ewu63@users.noreply.github.com> Date: Tue, 1 Apr 2025 22:10:59 -0700 Subject: [PATCH 10/16] PKG_CONFIG_PATH --- .github/build_real.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/build_real.sh b/.github/build_real.sh index 0482dcc30..abb1b3c51 100755 --- a/.github/build_real.sh +++ b/.github/build_real.sh @@ -11,6 +11,10 @@ fi mv ~/.config/pip/constraints.txt ~/.config/pip/constraints.txt.bkup touch ~/.config/pip/constraints.txt +# set $PKG_CONFIG_PATH so pkg-config can find ipopt +# only necessary if IPOPT is installed outside of conda +export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$IPOPT_DIR/lib/pkgconfig + pip install .[optview,testing] -v # move pip constraints file back From bae881a2c5fa560c453f03c81a1f8153ababed47 Mon Sep 17 00:00:00 2001 From: Ella Wu <602725+ewu63@users.noreply.github.com> Date: Tue, 1 Apr 2025 22:57:40 -0700 Subject: [PATCH 11/16] update docs --- doc/install.rst | 13 -------- doc/optimizers/IPOPT.rst | 67 +++------------------------------------- 2 files changed, 5 insertions(+), 75 deletions(-) diff --git a/doc/install.rst b/doc/install.rst index 4b1ea597a..fe1b5424c 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -184,19 +184,6 @@ An ``environment.yml`` file is provided in the ``pyoptsparse`` repo: conda config --env --set channel_priority strict conda env update -f .github\environment.yml - conda install libpgmath - -Next, we need to tell the compiler where to find IPOPT: - -.. tabs:: - - .. code-tab:: bash Linux/OSX - - export IPOPT_DIR="$CONDA_PREFIX" - - .. code-tab:: powershell Windows - - set IPOPT_DIR=%CONDA_PREFIX%\Library Finally, build the wheel and install it using pip: diff --git a/doc/optimizers/IPOPT.rst b/doc/optimizers/IPOPT.rst index dd2ff586c..e68aec044 100644 --- a/doc/optimizers/IPOPT.rst +++ b/doc/optimizers/IPOPT.rst @@ -4,71 +4,14 @@ IPOPT ===== IPOPT (Interior Point OPTimizer) is an open source interior point optimizer, designed for large-scale nonlinear optimization. The source code can be found `here `_. -The latest version we support is 3.13.2. +The latest version we support is 3.14.17. Installation ------------ -IPOPT must be installed separately, then linked to pyOptSparse when building. -For the full installation instructions, please see `their documentation `_. -OpenMDAO also has a very helpful `script `_ which can be used to install IPOPT with other linear solvers. -Here we explain a basic setup using MUMPS as the linear solver, together with METIS adapted from the OpenMDAO script. - -#. Download the tarball and extract it to ``$IPOPT_DIR`` which could be set to for example ``$HOME/packages/Ipopt``. - -#. Install METIS, which can be used to improve the performance of the MUMPS linear solver. - - .. code-block:: bash - - # build METIS - cd $IPOPT_DIR - git clone https://github.com/coin-or-tools/ThirdParty-Metis.git - cd ThirdParty-Metis - ./get.Metis - ./configure --prefix=$IPOPT_DIR - make - make install - -#. Install MUMPS - - .. code-block:: bash - - # build MUMPS - cd $IPOPT_DIR - git clone https://github.com/coin-or-tools/ThirdParty-Mumps.git - cd ThirdParty-Mumps - ./get.Mumps - ./configure --with-metis --with-metis-lflags="-L${IPOPT_DIR}/lib -lcoinmetis" \ - --with-metis-cflags="-I${IPOPT_DIR}/include -I${IPOPT_DIR}/include/coin-or -I${IPOPT_DIR}/include/coin-or/metis" \ - --prefix=$IPOPT_DIR CFLAGS="-I${IPOPT_DIR}/include -I${IPOPT_DIR}/include/coin-or -I${IPOPT_DIR}/include/coin-or/metis" \ - FCFLAGS="-I${IPOPT_DIR}/include -I${IPOPT_DIR}/include/coin-or -I${IPOPT_DIR}/include/coin-or/metis" - make - make install - -#. Build IPOPT - - .. code-block:: bash - - # build IPOPT - cd $IPOPT_DIR - mkdir build - cd build - ../configure --prefix=${IPOPT_DIR} --disable-java --with-mumps --with-mumps-lflags="-L${IPOPT_DIR}/lib -lcoinmumps" \ - --with-mumps-cflags="-I${IPOPT_DIR}/include/coin-or/mumps" - make - make install - -#. You must add the IPOPT library path to the ``LD_LIBRARY_PATH`` variable for things to work right. - This could be done for example by adding the following to your ``.bashrc``: - - .. code-block:: bash - - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$IPOPT_DIR/lib - - Furthermore, the environment variable ``$IPOPT_DIR`` must be set correctly in order to link to pyOptSparse. - Alternatively, you can manually define the variables ``$IPOPT_LIB`` and ``$IPOPT_INC`` for the lib and include paths separately. - - -#. Now clean build pyOptSparse. Verify that IPOPT works by running the relevant tests. +IPOPT and its Python interface `cyipopt ` must be installed separately. +Follow the instructions `here `_. +OpenMDAO also has a very helpful `script `_ which can be used to install IPOPT with other linear solvers, +but it does not install ``cyipopt`` for you. Options ------- From 95f06fe03a71bf369e76eb0afaba4c91a7ff9633 Mon Sep 17 00:00:00 2001 From: kanekosh Date: Fri, 4 Apr 2025 20:07:36 -0400 Subject: [PATCH 12/16] return array of nans under analysis failure --- pyoptsparse/pyIPOPT/pyIPOPT.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyoptsparse/pyIPOPT/pyIPOPT.py b/pyoptsparse/pyIPOPT/pyIPOPT.py index 3278f1a7c..4f0de8bea 100644 --- a/pyoptsparse/pyIPOPT/pyIPOPT.py +++ b/pyoptsparse/pyIPOPT/pyIPOPT.py @@ -211,7 +211,7 @@ class CyIPOPTProblem: def objective(_, x): fobj, fail = self._masterFunc(x, ["fobj"]) if fail == 1: - fobj = np.array(np.NaN) + fobj = np.array(np.nan) elif fail == 2: self.userRequestedTermination = True return fobj @@ -219,7 +219,7 @@ def objective(_, x): def constraints(_, x): fcon, fail = self._masterFunc(x, ["fcon"]) if fail == 1: - fcon = np.array(np.NaN) + fcon = np.full_like(fcon, np.nan) elif fail == 2: self.userRequestedTermination = True return fcon.copy() @@ -227,7 +227,7 @@ def constraints(_, x): def gradient(_, x): gobj, fail = self._masterFunc(x, ["gobj"]) if fail == 1: - gobj = np.array(np.NaN) + gobj = np.full_like(gobj, np.nan) elif fail == 2: self.userRequestedTermination = True return gobj.copy() @@ -235,7 +235,7 @@ def gradient(_, x): def jacobian(_, x): gcon, fail = self._masterFunc(x, ["gcon"]) if fail == 1: - gcon = np.array(np.NaN) + gcon = np.full_like(gcon, np.nan) elif fail == 2: self.userRequestedTermination = True return gcon.copy() From ef5fd77d557f118cd18ea7527f10951550c23ed3 Mon Sep 17 00:00:00 2001 From: kanekosh Date: Sat, 5 Apr 2025 14:36:25 -0400 Subject: [PATCH 13/16] added tests to check eval failure --- tests/test_hs015.py | 67 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/tests/test_hs015.py b/tests/test_hs015.py index 60b73d723..357e1bd10 100644 --- a/tests/test_hs015.py +++ b/tests/test_hs015.py @@ -278,22 +278,83 @@ def test_snopt_work_arrays_save(self): os.remove(pickleFile) os.remove(histFile) - def test_snopt_failed_initial(self): + @parameterized.expand(["SNOPT", "IPOPT"]) + def test_failed_initial_func(self, optName): + # Test if we get the correct inform when the initial function call fails def failed_fun(x_dict): funcs = {"obj": 0.0, "con": [np.nan, np.nan]} fail = True return funcs, fail - self.optName = "SNOPT" + self.optName = optName self.setup_optProb() # swap obj to report NaN self.optProb.objFun = failed_fun sol = self.optimize(optOptions={}, storeHistory=True) - self.assert_inform_equal(sol, optInform=61) + if self.optName == "SNOPT": + inform_ref = 61 + elif self.optName == "IPOPT": + inform_ref = -13 + self.assert_inform_equal(sol, optInform=inform_ref) # make sure empty history does not error out hist = History(self.histFileName, flag="r") hist.getValues() + @parameterized.expand(["SNOPT", "IPOPT"]) + def test_failed_initial_sens(self, optName): + # Test if we get the correct inform when the initial sensitivity call fails + def failed_sens(xdict, funcs): + funcsSens = {} + funcsSens["obj"] = {"xvars": [np.nan, np.nan]} + funcsSens["con"] = {"xvars": [[np.nan, np.nan], [np.nan, np.nan]]} + fail = True + return funcsSens, fail + + self.optName = optName + self.setup_optProb() + sol = self.optimize(sens=failed_sens, optOptions={}, storeHistory=True) + if self.optName == "SNOPT": + inform_ref = 61 + elif self.optName == "IPOPT": + inform_ref = -13 + self.assert_inform_equal(sol, optInform=inform_ref) + # make sure empty history does not error out + hist = History(self.histFileName, flag="r") + hist.getValues() + + @parameterized.expand(["SNOPT", "IPOPT", "SLSQP", "PSQP", "NLPQLP", "ParOpt"]) + def test_func_eval_failure(self, optName): + # Test if optimizer back-tracks after function evaluation failure and keeps going + + # This function is the same as original except it will fail at 3rd call + def objFun_eval_failure(x_dict): + self.nf += 1 + x = x_dict["xvars"] + funcs = {} + funcs["obj"] = [100 * (x[1] - x[0] ** 2) ** 2 + (1 - x[0]) ** 2] + conval = np.zeros(2, "D") + conval[0] = x[0] * x[1] + conval[1] = x[0] + x[1] ** 2 + funcs["con"] = conval + # extra keys + funcs["extra1"] = 0.0 + funcs["extra2"] = 1.0 + fail = False + if self.nf == 3: + funcs["obj"] = [np.nan] + funcs["con"] = [np.nan, np.nan] + fail = True + return funcs, fail + + self.optName = optName + self.setup_optProb() + # swap obj to simulate eval failure + self.optProb.objFun = objFun_eval_failure + sol = self.optimize(optOptions={}, storeHistory=True) + # sheck solution and informs + self.assert_solution_allclose(sol, 1e-5) + self.assert_inform_equal(sol) + if __name__ == "__main__": unittest.main() From 1fec43b30b1ae3b58360f7198dfe4d9cd6f5c8df Mon Sep 17 00:00:00 2001 From: kanekosh Date: Sat, 5 Apr 2025 14:50:21 -0400 Subject: [PATCH 14/16] only test SNOPT and IPOPT in func eval failure test --- tests/test_hs015.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_hs015.py b/tests/test_hs015.py index 357e1bd10..3b00f4562 100644 --- a/tests/test_hs015.py +++ b/tests/test_hs015.py @@ -322,7 +322,7 @@ def failed_sens(xdict, funcs): hist = History(self.histFileName, flag="r") hist.getValues() - @parameterized.expand(["SNOPT", "IPOPT", "SLSQP", "PSQP", "NLPQLP", "ParOpt"]) + @parameterized.expand(["SNOPT", "IPOPT"]) def test_func_eval_failure(self, optName): # Test if optimizer back-tracks after function evaluation failure and keeps going From 452dd7dbfbb520488e7eb14bc8551e41409af4b5 Mon Sep 17 00:00:00 2001 From: Ella Wu <602725+ewu63@users.noreply.github.com> Date: Sat, 5 Apr 2025 14:42:04 -0700 Subject: [PATCH 15/16] clean up meson.build again --- pyoptsparse/meson.build | 1 - pyoptsparse/pyALPSO/meson.build | 24 ++++++++++++------------ 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/pyoptsparse/meson.build b/pyoptsparse/meson.build index c8394c6c6..7f1d811bc 100644 --- a/pyoptsparse/meson.build +++ b/pyoptsparse/meson.build @@ -66,7 +66,6 @@ inc_f2py = include_directories(incdir_f2py) #) subdir('pySNOPT') -# subdir('pyIPOPT') subdir('pySLSQP') subdir('pyCONMIN') subdir('pyNLPQLP') diff --git a/pyoptsparse/pyALPSO/meson.build b/pyoptsparse/pyALPSO/meson.build index 6c87b5030..b49fafe2d 100644 --- a/pyoptsparse/pyALPSO/meson.build +++ b/pyoptsparse/pyALPSO/meson.build @@ -1,13 +1,13 @@ -# python_sources = [ -# '__init__.py', -# 'alpso.py', -# 'alpso_ext.py', -# 'pyALPSO.py', -# 'LICENSE' -# ] +python_sources = [ + '__init__.py', + 'alpso.py', + 'alpso_ext.py', + 'pyALPSO.py', + 'LICENSE' +] -# py3_target.install_sources( -# python_sources, -# pure: true, -# subdir: 'pyoptsparse/pyALPSO' -# ) \ No newline at end of file +py3_target.install_sources( + python_sources, + pure: true, + subdir: 'pyoptsparse/pyALPSO' +) \ No newline at end of file From 14734ab16a6688ced9df283fa9571833e57e1997 Mon Sep 17 00:00:00 2001 From: kanekosh Date: Sat, 5 Apr 2025 19:46:06 -0400 Subject: [PATCH 16/16] typo --- tests/test_hs015.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_hs015.py b/tests/test_hs015.py index 3b00f4562..ffc15e7c7 100644 --- a/tests/test_hs015.py +++ b/tests/test_hs015.py @@ -351,7 +351,7 @@ def objFun_eval_failure(x_dict): # swap obj to simulate eval failure self.optProb.objFun = objFun_eval_failure sol = self.optimize(optOptions={}, storeHistory=True) - # sheck solution and informs + # check solution and informs self.assert_solution_allclose(sol, 1e-5) self.assert_inform_equal(sol)