├── .gitignore ├── Changelog ├── README.md ├── examples ├── hs071.py ├── rosen.py └── unconstrained │ ├── himmelblau.py │ ├── rosen.py │ └── wood.py ├── pyipoptpackage ├── __init__.py ├── ipoptconst.py └── ipoptunconstrained.py ├── setup.py └── src ├── callback.c ├── hook.h └── pyipoptcoremodule.c /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.html 3 | # EXCEPTIONS 4 | !README 5 | !.gitignore 6 | build 7 | pyipopt.so 8 | -------------------------------------------------------------------------------- /Changelog: -------------------------------------------------------------------------------- 1 | List of changes 2 | 3 | Version 0.1 List works 4 | 5 | Version 0.2 Use Numpy arrays instead of lists for efficiency 6 | 7 | Version 0.3 Change the module interface to allocate more nlp instances instead of one 8 | now use nlp = pyipopt.create(xxx) 9 | and nlp.solve 10 | nlp.close() 11 | now we can create multiple instance of nlp. [Tested] 12 | 13 | Version 0.4 Move all the pointers to the 14 | PyObject (callback function in Python) to the user_data field 15 | Therefore, the C callback function here can just dispatch it to the Python 16 | callable object in the user_data [DONE] 17 | [We wrap the user_data twice] 18 | 19 | Version 0.5: Bug in H matrix fixed 20 | 21 | Version 0.5.1: some stupid spelling error in the source file and comments fixed. 22 | 23 | Version 0.6: Fixed a memory leak problem (at least valgrind won't complain). 24 | 25 | Version 0.7: Fixed a bug for the value of m and n, a bug for reference counting. 26 | 27 | Version 0.8: Merged patches submitted by others, now supporting intermediate_callback (requires Ipopt>=3.9.1) 28 | 29 | Version 0.8.1: Updated the README doc. 30 | 31 | Version 0.8.2: Merged a change from Guillaume Jacquenot, now there is no GOTO in the source code. 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PyIpopt 2 | ======= 3 | 4 | PyIpopt is a python module that allows you to use [Ipopt](http://www.coin-or.org/Ipopt/) in Python. It is developed by Eric Xu when he was a PhD student at [Washington University](https://wustl.edu/) and issued under the BSD license. 5 | 6 | Installation 7 | ------------ 8 | 9 | ### Dependencies 10 | 11 | PyIpopt depends on the following packages: 12 | 13 | 1. A compiler and a linker, e.g. gcc, ld 14 | 2. [Ipopt](https://projects.coin-or.org/Ipopt) 15 | 3. [Numpy](http://numpy.scipy.org/) 16 | 4. Python.h (part of the python source code, you can download it from [Python.org](http://python.org)) 17 | 18 | ### Install 19 | 20 | First, get the latest source code using: 21 | 22 | $ git clone http://github.com/xuy/pyipopt.git 23 | 24 | In your PyIpopt folder, edit setup.py to reflect the configuration of your system, then do 25 | 26 | $ python setup.py build 27 | $ sudo python setup.py install 28 | 29 | ### Test 30 | 31 | $ python hs071.py 32 | 33 | You should be able to see the result of solving the toy problem. 34 | 35 | Usage 36 | ----- 37 | You can use PyIpopt like this: 38 | 39 | import pyipopt 40 | # define your call back functions 41 | nlp = pyipopt.create(...) 42 | nlp.solve(...) 43 | nlp.close() 44 | 45 | You can also check out hs071.py to see how to use PyIpopt. 46 | 47 | PyIpopt as a module comes with docstring. You can poke around 48 | it by using Python's $help()$ command. 49 | 50 | Testing 51 | ------- 52 | 53 | I have included an example 54 | 55 | To see if you have PyIpopt ready, use the following command under the pyipopt's directory. 56 | 57 | python hs071.py 58 | 59 | The file "hs071.py" contains a toy optimization problem. If everything is OK, pyipopt will invoke Ipopt to solve it for you. This python file is self-documented and can be used as a template for writing your own optimization problems. 60 | 61 | Pyipopt is a legitimate Python module, you can inspect it by using standard Python commands like "dir" or "help". All functions in pyipopt are documented in details. 62 | 63 | **Hessian Estimation**: since Hessian estimation is usually tedious, Ipopt can solve problems without Hessian estimation. Pyipopt also supports this feature. The file "hs071.py" demonstrates the idea. If you provide the pyipopt.create function with an "eval_h" callback function as well as the "apply_new" callback function, Ipopt will delegate the Hessian matrix calculation to your function (otherwise Ipopt will approximate Hessian for you). 64 | 65 | Contributing 66 | ------------ 67 | 68 | 1. Fork it. 69 | 2. Create a branch (`git checkout -b my_pyipopt`) 70 | 3. Commit your changes (`git commit -am "your awesome message"`) 71 | 4. Push to the branch (`git push origin my_pyipopt`) 72 | 5. Create a pull request 73 | 6. Nag me about it if I am lazy. 74 | 75 | Troubleshooting 76 | --------------- 77 | 78 | ### Check Ipopt 79 | 80 | PyIpopt links to Ipopt's C library. If that library is not available PyIpopt will fail 81 | during module initialization. To check the availability of this library, you can go to 82 | $IPOPT_DIR/Ipopt/examples/hs071_c/ 83 | and issue $make to ensure you can compile and run the toy example supplied by Ipopt. 84 | 85 | ### Miscellaneous problems 86 | 87 | * Error: 88 | import pyipopt 89 | ImportError: can not find libipopt.so.0 90 | 91 | * Solution: 92 | find it and copy it to a folder that ld can access 93 | 94 | * Error: 95 | import pyipopt 96 | ImportError: /usr/lib/libipopt.so.0: undefined symbol: _gfortran_XXX 97 | 98 | * Solution: 99 | check if your `hs071_c` example work. It is very likely that your ipopt library is not correctly compiled. 100 | 101 | 102 | * Error: 103 | import pyipopt 104 | ImportError: /usr/lib/libipopt.so.0: undefined symbol: SetIntermediateCallback 105 | 106 | * Solution: 107 | SetIntermediateCallback is a function added since Ipopt 3.9.1. (see https://projects.coin-or.org/Ipopt/changeset/1830 ) 108 | Make sure you have an Ipopt version >= 3.9.1 109 | 110 | * Error: 111 | import pyipopt 112 | ImportError: /usr/lib/libipopt.so.0: undefined symbol: ma19ad_ 113 | 114 | * Solution: 115 | First, use 116 | nm /usr/lib/libipopt.so.0 | grep ma19ad_ 117 | to see if it is marked with U. It should. This means that libipopt.so.0 is not aware of libcoinhsl.so.0. You can fix this 118 | by adding -lcoinhsl in the makefile of pyipopt. It seems to me that this happens in the recent versions of ipopt. Eventually 119 | pyipopt will have a better building mechanism, and I will fix this soon. 120 | 121 | * Error: 122 | import pyipopt 123 | ImportError: /usr/lib/libipopt.so.0: undefined symbol: SomeKindOfSymbol 124 | 125 | * Solution: 126 | I can assure you that it is NOT a bug of pyipopt. It is very likely that you did not link the right package when compiling pyipopt. 127 | First, use 128 | nm /usr/lib/libipopt.so.0 | grep SomeKindOfSymbol 129 | to see if this symbol is indeed missing. Do a Google search to find the library file, and 130 | add -lWhateverLibrary in the makefile of pyipopt. 131 | 132 | Ipopt is built using various third-party libraries. Different machines may have different set of libraries. You should 133 | try to locate these dependencies and indicate them when compiling pyipopt. This is just a limitation of dynamic linking libraries and 134 | is not related to Pyipopt. Please do not report a missing symbol error as a "bug" to me unless you are 100% sure it is the problem of pyipopt. 135 | 136 | 137 | Contact 138 | -------- 139 | 140 | Eric Xu 141 | 142 | Software Engineer @ Google 143 | 144 | 145 | -------------------------------------------------------------------------------- /examples/hs071.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Author: Eric Xu. Washington University 4 | # The same model as Ipopt/examples/hs071 5 | from __future__ import print_function 6 | 7 | import pyipopt 8 | from numpy import * 9 | 10 | nvar = 4 11 | x_L = ones((nvar), dtype=float_) * 1.0 12 | x_U = ones((nvar), dtype=float_) * 5.0 13 | 14 | ncon = 2 15 | 16 | g_L = array([25.0, 40.0]) 17 | g_U = array([2.0*pow(10.0, 19), 40.0]) 18 | 19 | def eval_f(x, user_data = None): 20 | assert len(x) == 4 21 | return x[0] * x[3] * (x[0] + x[1] + x[2]) + x[2] 22 | 23 | def eval_grad_f(x, user_data = None): 24 | assert len(x) == 4 25 | grad_f = array([ 26 | x[0] * x[3] + x[3] * (x[0] + x[1] + x[2]) , 27 | x[0] * x[3], 28 | x[0] * x[3] + 1.0, 29 | x[0] * (x[0] + x[1] + x[2]) 30 | ], float_) 31 | return grad_f; 32 | 33 | def eval_g(x, user_data= None): 34 | assert len(x) == 4 35 | return array([ 36 | x[0] * x[1] * x[2] * x[3], 37 | x[0]*x[0] + x[1]*x[1] + x[2]*x[2] + x[3]*x[3] 38 | ], float_) 39 | 40 | nnzj = 8 41 | def eval_jac_g(x, flag, user_data = None): 42 | if flag: 43 | return (array([0, 0, 0, 0, 1, 1, 1, 1]), 44 | array([0, 1, 2, 3, 0, 1, 2, 3])) 45 | else: 46 | assert len(x) == 4 47 | return array([ x[1]*x[2]*x[3], 48 | x[0]*x[2]*x[3], 49 | x[0]*x[1]*x[3], 50 | x[0]*x[1]*x[2], 51 | 2.0*x[0], 52 | 2.0*x[1], 53 | 2.0*x[2], 54 | 2.0*x[3] ]) 55 | 56 | nnzh = 10 57 | def eval_h(x, lagrange, obj_factor, flag, user_data = None): 58 | if flag: 59 | hrow = [0, 1, 1, 2, 2, 2, 3, 3, 3, 3] 60 | hcol = [0, 0, 1, 0, 1, 2, 0, 1, 2, 3] 61 | return (array(hcol), array(hrow)) 62 | else: 63 | values = zeros((10), float_) 64 | values[0] = obj_factor * (2*x[3]) 65 | values[1] = obj_factor * (x[3]) 66 | values[2] = 0 67 | values[3] = obj_factor * (x[3]) 68 | values[4] = 0 69 | values[5] = 0 70 | values[6] = obj_factor * (2*x[0] + x[1] + x[2]) 71 | values[7] = obj_factor * (x[0]) 72 | values[8] = obj_factor * (x[0]) 73 | values[9] = 0 74 | values[1] += lagrange[0] * (x[2] * x[3]) 75 | 76 | values[3] += lagrange[0] * (x[1] * x[3]) 77 | values[4] += lagrange[0] * (x[0] * x[3]) 78 | 79 | values[6] += lagrange[0] * (x[1] * x[2]) 80 | values[7] += lagrange[0] * (x[0] * x[2]) 81 | values[8] += lagrange[0] * (x[0] * x[1]) 82 | values[0] += lagrange[1] * 2 83 | values[2] += lagrange[1] * 2 84 | values[5] += lagrange[1] * 2 85 | values[9] += lagrange[1] * 2 86 | return values 87 | 88 | def apply_new(x): 89 | return True 90 | 91 | nlp = pyipopt.create(nvar, x_L, x_U, ncon, g_L, g_U, nnzj, nnzh, eval_f, eval_grad_f, eval_g, eval_jac_g) 92 | 93 | x0 = array([1.0, 5.0, 5.0, 1.0]) 94 | pi0 = array([1.0, 1.0]) 95 | 96 | """ 97 | print x0 98 | print nvar, ncon, nnzj 99 | print x_L, x_U 100 | print g_L, g_U 101 | print eval_f(x0) 102 | print eval_grad_f(x0) 103 | print eval_g(x0) 104 | a = eval_jac_g(x0, True) 105 | print "a = ", a[1], a[0] 106 | print eval_jac_g(x0, False) 107 | print eval_h(x0, pi0, 1.0, False) 108 | print eval_h(x0, pi0, 1.0, True) 109 | """ 110 | 111 | """ You can set Ipopt options by calling nlp.num_option, nlp.str_option 112 | or nlp.int_option. For instance, to set the tolarance by calling 113 | 114 | nlp.num_option('tol', 1e-8) 115 | 116 | For a complete list of Ipopt options, refer to 117 | 118 | http://www.coin-or.org/Ipopt/documentation/node59.html 119 | 120 | Note that Ipopt distinguishs between Int, Num, and Str options, yet sometimes 121 | does not explicitly tell you which option is which. If you are not sure about 122 | the option's type, just try it in PyIpopt. If you try to set one type of 123 | option using the wrong function, Pyipopt will remind you of it. """ 124 | 125 | print("Going to call solve") 126 | print("x0 = {}".format(x0)) 127 | x, zl, zu, constraint_multipliers, obj, status = nlp.solve(x0) 128 | # import pdb; pdb.set_trace() 129 | nlp.close() 130 | 131 | def print_variable(variable_name, value): 132 | for i in range(len(value)): 133 | print("{} {}".format(variable_name + "["+str(i)+"] =", value[i])) 134 | 135 | print("Solution of the primal variables, x") 136 | print_variable("x", x) 137 | 138 | print("Solution of the bound multipliers, z_L and z_U") 139 | print_variable("z_L", zl) 140 | print_variable("z_U", zu) 141 | 142 | print("Solution of the constraint multipliers, lambda") 143 | print_variable("lambda", constraint_multipliers) 144 | 145 | print("Objective value") 146 | print("f(x*) = {}".format(obj)) 147 | 148 | -------------------------------------------------------------------------------- /examples/rosen.py: -------------------------------------------------------------------------------- 1 | """ 2 | Is the hessian even supported by pyipopt? 3 | 4 | There is a comment here 5 | http://www.wstein.org/home/wstein/www/home/was/patches/ 6 | openopt-0.24/src/openopt/solvers/CoinOr/ipopt_oo.py 7 | suggesting that the pyipopt hessian support may be buggy. 8 | Also check some bug reports here: 9 | http://code.google.com/p/pyipopt/issues/list 10 | ?can=1&q=&colspec=ID+Type+Status+Priority+Milestone+Owner+Summary&cells=tiles 11 | """ 12 | from __future__ import print_function 13 | 14 | import numpy 15 | import scipy.optimize 16 | import pyipopt 17 | 18 | def eval_f(X, user_data=None): 19 | """ 20 | Directly evaluate the objective function f. 21 | """ 22 | return scipy.optimize.rosen(X) 23 | 24 | def eval_grad_f(X, user_data=None): 25 | """ 26 | Evaluate the gradient of the objective function f. 27 | """ 28 | return scipy.optimize.rosen_der(X) 29 | 30 | def eval_g(X, user_data=None): 31 | """ 32 | Evaluate the constraint functions. 33 | """ 34 | return numpy.array([], dtype=float) 35 | 36 | def eval_jac_g(X, flag, user_data=None): 37 | """ 38 | Evaluate the sparse Jacobian of constraint functions g. 39 | @param X: parameter values 40 | @param flag: this asks for the sparsity structure 41 | """ 42 | print('eval_jac_g') 43 | print(X) 44 | print(flag) 45 | print(user_data) 46 | print() 47 | #XXX 48 | if flag: 49 | rows = numpy.array([], dtype=int) 50 | cols = numpy.array([], dtype=int) 51 | return (rows, cols) 52 | else: 53 | return numpy.array([], dtype=float) 54 | 55 | def eval_h(X, lagrange, obj_factor, flag, user_data=None): 56 | """ 57 | Evaluate the sparse hessian of the Lagrangian. 58 | @param X: parameter values 59 | @param lagrange: something about the constraints 60 | @param obj_factor: no clue what this is 61 | @param flag: this asks for the sparsity structure 62 | """ 63 | #XXX 64 | print('eval_h:') 65 | print(X) 66 | print(lagrange) 67 | print(obj_factor) 68 | print(flag) 69 | print(user_data) 70 | print() 71 | rows = numpy.array([0, 1, 1], dtype=int) 72 | cols = numpy.array([0, 0, 1], dtype=int) 73 | if flag: 74 | return (rows, cols) 75 | else: 76 | # XXX 77 | # these values are meaningless 78 | values = numpy.zeros(3, dtype=float) 79 | #values[0] = obj_factor*2 80 | #values[1] = 0 81 | #values[2] = obj_factor*2 82 | H = scipy.optimize.rosen_hess(X) 83 | for i, (r, c) in enumerate(zip(rows, cols)): 84 | values[i] = H[r, c] * obj_factor 85 | return values 86 | 87 | def apply_new(X): 88 | """ 89 | What is this? 90 | """ 91 | #XXX 92 | print('apply_new:') 93 | print(X) 94 | print() 95 | return True 96 | 97 | 98 | def main(): 99 | 100 | # verbose 101 | pyipopt.set_loglevel(2) 102 | 103 | # define the parameters and their box constraints 104 | nvar = 2 105 | x_L = numpy.array([-3, -3], dtype=float) 106 | x_U = numpy.array([3, 3], dtype=float) 107 | 108 | # define the inequality constraints 109 | ncon = 0 110 | g_L = numpy.array([], dtype=float) 111 | g_U = numpy.array([], dtype=float) 112 | 113 | # define the number of nonzeros in the jacobian and in the hessian 114 | # there are no nonzeros in the constraint jacobian 115 | nnzj = 0 116 | 117 | # there are maximum nonzeros (nvar*(nvar+1))/2 in the lagrangian hessian 118 | nnzh = 3 119 | 120 | # create the nonlinear programming model 121 | nlp = pyipopt.create( 122 | nvar, 123 | x_L, 124 | x_U, 125 | ncon, 126 | g_L, 127 | g_U, 128 | nnzj, 129 | nnzh, 130 | eval_f, 131 | eval_grad_f, 132 | eval_g, 133 | eval_jac_g, 134 | eval_h, 135 | apply_new, 136 | ) 137 | 138 | # define the initial guess 139 | x0 = numpy.array([-1.2, 1], dtype=float) 140 | 141 | # compute the results using ipopt 142 | results = nlp.solve(x0) 143 | 144 | # free the model 145 | nlp.close() 146 | 147 | # report the results 148 | print(results) 149 | 150 | 151 | if __name__ == '__main__': 152 | main() 153 | 154 | -------------------------------------------------------------------------------- /examples/unconstrained/himmelblau.py: -------------------------------------------------------------------------------- 1 | """ 2 | Minimize a standard unconstrained test function. 3 | 4 | This example uses algopy for the gradient and hessian. 5 | """ 6 | from __future__ import print_function 7 | 8 | import functools 9 | 10 | import numpy 11 | import algopy 12 | 13 | import pyipopt 14 | 15 | def himmelblau(X): 16 | """ 17 | http://en.wikipedia.org/wiki/Himmelblau%27s_function 18 | This function has four local minima where the value of the function is 0. 19 | """ 20 | x = X[0] 21 | y = X[1] 22 | a = x*x + y - 11 23 | b = x + y*y - 7 24 | return a*a + b*b 25 | 26 | def eval_grad(f, theta): 27 | theta = algopy.UTPM.init_jacobian(theta) 28 | return algopy.UTPM.extract_jacobian(f(theta)) 29 | 30 | def eval_hess(f, theta): 31 | theta = algopy.UTPM.init_hessian(theta) 32 | return algopy.UTPM.extract_hessian(len(theta), f(theta)) 33 | 34 | def main(): 35 | pyipopt.set_loglevel(2) 36 | x0 = numpy.array([-0.27, -0.9], dtype=float) 37 | results = pyipopt.fmin_unconstrained( 38 | himmelblau, 39 | x0, 40 | fprime=functools.partial(eval_grad, himmelblau), 41 | fhess=functools.partial(eval_hess, himmelblau), 42 | ) 43 | print(results) 44 | 45 | if __name__ == '__main__': 46 | main() 47 | -------------------------------------------------------------------------------- /examples/unconstrained/rosen.py: -------------------------------------------------------------------------------- 1 | """ 2 | Minimize the Rosenbrock function with the unconstrained minimization interface. 3 | 4 | See the rosen.py example for more details. 5 | """ 6 | from __future__ import print_function 7 | 8 | import numpy 9 | import scipy.optimize 10 | 11 | import pyipopt 12 | 13 | def main(): 14 | pyipopt.set_loglevel(2) 15 | x0 = numpy.array([-1.2, 1], dtype=float) 16 | results = pyipopt.fmin_unconstrained( 17 | scipy.optimize.rosen, 18 | x0, 19 | fprime=scipy.optimize.rosen_der, 20 | fhess=scipy.optimize.rosen_hess, 21 | ) 22 | print(results) 23 | 24 | if __name__ == '__main__': 25 | main() 26 | -------------------------------------------------------------------------------- /examples/unconstrained/wood.py: -------------------------------------------------------------------------------- 1 | """ 2 | Minimize a standard unconstrained test function. 3 | 4 | This example uses algopy for the gradient and hessian. 5 | """ 6 | from __future__ import print_function 7 | 8 | import functools 9 | 10 | import numpy 11 | import algopy 12 | 13 | import pyipopt 14 | 15 | def wood(X): 16 | """ 17 | The minimum is at [1, 1, 1, 1]. 18 | """ 19 | x1 = X[0] 20 | x2 = X[1] 21 | x3 = X[2] 22 | x4 = X[3] 23 | return sum(( 24 | 100*(x1*x1 - x2)**2, 25 | (x1-1)**2, 26 | (x3-1)**2, 27 | 90*(x3*x3 - x4)**2, 28 | 10.1*((x2-1)**2 + (x4-1)**2), 29 | 19.8*(x2-1)*(x4-1), 30 | )) 31 | 32 | def eval_grad(f, theta): 33 | theta = algopy.UTPM.init_jacobian(theta) 34 | return algopy.UTPM.extract_jacobian(f(theta)) 35 | 36 | def eval_hess(f, theta): 37 | theta = algopy.UTPM.init_hessian(theta) 38 | return algopy.UTPM.extract_hessian(len(theta), f(theta)) 39 | 40 | def main(): 41 | pyipopt.set_loglevel(2) 42 | x0 = numpy.array([-3, -1, -3, -1], dtype=float) 43 | results = pyipopt.fmin_unconstrained( 44 | wood, 45 | x0, 46 | fprime=functools.partial(eval_grad, wood), 47 | fhess=functools.partial(eval_hess, wood), 48 | ) 49 | print(results) 50 | 51 | if __name__ == '__main__': 52 | main() 53 | -------------------------------------------------------------------------------- /pyipoptpackage/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is a package for a python interface to ipopt. 3 | 4 | The underlying C interface is in pyipoptcore. 5 | """ 6 | import os 7 | import sys 8 | 9 | sys.path.append(os.path.dirname(__file__)) 10 | 11 | import functools 12 | 13 | import numpy 14 | 15 | from ipoptconst import * 16 | from pyipoptcore import * 17 | from ipoptunconstrained import fmin_unconstrained 18 | 19 | # verbose messages from the C interface 20 | set_loglevel(2) 21 | 22 | -------------------------------------------------------------------------------- /pyipoptpackage/ipoptconst.py: -------------------------------------------------------------------------------- 1 | """ 2 | These are some constants. 3 | """ 4 | 5 | # http://www.coin-or.org/Ipopt/documentation/node35.html 6 | # FIXME: these are not actually constant but may be changed within ipopt 7 | NLP_LOWER_BOUND_INF = -1e19 8 | NLP_UPPER_BOUND_INF = 1e19 9 | -------------------------------------------------------------------------------- /pyipoptpackage/ipoptunconstrained.py: -------------------------------------------------------------------------------- 1 | """ 2 | Unconstrained function minimization. 3 | 4 | This is supposed to have an interface like the old scipy.optimize interface. 5 | The underlying C interface is in pyipoptcore. 6 | """ 7 | 8 | import functools 9 | 10 | import numpy 11 | 12 | import pyipoptcore 13 | from ipoptconst import NLP_LOWER_BOUND_INF 14 | from ipoptconst import NLP_UPPER_BOUND_INF 15 | 16 | 17 | def _eval_g(X, user_data=None): 18 | return numpy.array([], dtype=float) 19 | 20 | def _eval_jac_g(X, flag, user_data=None): 21 | rows = numpy.array([], dtype=int) 22 | cols = numpy.array([], dtype=int) 23 | if flag: 24 | return (rows, cols) 25 | else: 26 | raise Exception( 27 | 'this should not be called for unconstrained optimization') 28 | 29 | def _eval_h( 30 | h, nvar, 31 | X, lagrange, obj_factor, flag, user_data=None): 32 | """ 33 | The first group of parameters should be applied using functools.partial. 34 | The second group of parameters are passed from ipopt. 35 | @param h: a function to compute the hessian. 36 | @param nvar: the number of parameters 37 | @param X: parameter values 38 | @param lagrange: something about the constraints 39 | @param obj_factor: no clue what this is 40 | @param flag: this asks for the sparsity structure 41 | @param user_data: please do not use this yet 42 | """ 43 | 44 | # Get the nonzero (row, column) entries of a lower triangular matrix. 45 | # This is related to the fact that the Hessian is symmetric, 46 | # and that ipopt is designed to work with sparse matrices. 47 | row_list = [] 48 | col_list = [] 49 | for row in range(nvar): 50 | for col in range(row+1): 51 | row_list.append(row) 52 | col_list.append(col) 53 | rows = numpy.array(row_list, dtype=int) 54 | cols = numpy.array(col_list, dtype=int) 55 | 56 | if flag: 57 | return (rows, cols) 58 | else: 59 | if nvar != len(X): 60 | raise Exception('parameter count mismatch') 61 | if lagrange: 62 | raise Exception('only unconstrained is implemented for now...') 63 | values = numpy.zeros(len(rows), dtype=float) 64 | H = h(X) 65 | for i, (r, c) in enumerate(zip(rows, cols)): 66 | #FIXME: am I using obj_factor correctly? 67 | # I don't really know what it is... 68 | values[i] = H[r, c] * obj_factor 69 | return values 70 | 71 | def _apply_new(X): 72 | #FIXME: I don't really know what this does, but ipopt wants it. 73 | return True 74 | 75 | def _create(f, nvar, fprime, fhess=None): 76 | """ 77 | Creates an ipopt nlp object. 78 | @param f: objective function to minimize 79 | @param nvar: number of parameters 80 | @param fprime: computes the gradient of the objective function 81 | @param fhess: computes the hessian of the objective function 82 | @return: a pyipopt nlp object which may be solved and then closed 83 | """ 84 | 85 | # no box constraints on the parameters 86 | x_L = numpy.array([NLP_LOWER_BOUND_INF]*nvar, dtype=float) 87 | x_U = numpy.array([NLP_UPPER_BOUND_INF]*nvar, dtype=float) 88 | 89 | # no other constraints 90 | ncon = 0 91 | g_L = numpy.array([], dtype=float) 92 | g_U = numpy.array([], dtype=float) 93 | 94 | # no constraint jacobian 95 | nnzj = 0 96 | 97 | # dense lower triangular hessian structure 98 | nnzh = 0 99 | if fhess: 100 | nnzh = (nvar * (nvar + 1)) // 2 101 | 102 | # define the nlp creation args 103 | nlp_args = [ 104 | nvar, 105 | x_L, 106 | x_U, 107 | ncon, 108 | g_L, 109 | g_U, 110 | nnzj, 111 | nnzh, 112 | f, 113 | fprime, 114 | _eval_g, 115 | _eval_jac_g, 116 | ] 117 | if fhess: 118 | nlp_args.extend([ 119 | functools.partial(_eval_h, fhess, nvar), 120 | _apply_new, 121 | ]) 122 | 123 | # create the nlp object 124 | return pyipoptcore.create(*nlp_args) 125 | 126 | def fmin_unconstrained(f, x0, fprime, fhess=None): 127 | """ 128 | This is a utility function wrapping create_unconstrained. 129 | @param f: objective function to minimize 130 | @param x0: initial guess 131 | @param fprime: computes the gradient of the objective function 132 | @param fhess: computes the hessian of the objective function 133 | @return: results in pyipoptcore format 134 | """ 135 | nvar = len(x0) 136 | nlp = _create(f, nvar, fprime, fhess) 137 | #FIXME: do something about this... 138 | #http://www.coin-or.org/Ipopt/documentation/node68.html 139 | nlp.num_option('tol', 1e-12) 140 | results = nlp.solve(x0) 141 | nlp.close() 142 | return results 143 | 144 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Originally contributed by Lorne McIntosh. 2 | # Modified by Eric Xu 3 | # Further modification by random internet people. 4 | 5 | # You will probably have to edit this file in unpredictable ways 6 | # if you want pyipopt to work for you, sorry. 7 | 8 | # When I installed Ipopt from source, I used the 9 | # --prefix=/usr/local 10 | # option, so this is where I want pyipopt to look for my ipopt installation. 11 | # I only installed from source because the ipopt packaging 12 | # for my linux distribution was buggy, 13 | # so by the time you read this the bugs have probably been fixed 14 | # and you will want to specify a different directory here. 15 | IPOPT_DIR = '/usr/local/' 16 | 17 | import os 18 | from distutils.core import setup 19 | from distutils.extension import Extension 20 | 21 | # NumPy is much easier to install than pyipopt, 22 | # and is a pyipopt dependency, so require it here. 23 | # We need it to tell us where the numpy header files are. 24 | import numpy 25 | numpy_include = numpy.get_include() 26 | 27 | # I personally do not need support for lib64 but I'm keeping it in the code. 28 | def get_ipopt_lib(): 29 | for lib_suffix in ('lib', 'lib64'): 30 | d = os.path.join(IPOPT_DIR, lib_suffix) 31 | if os.path.isdir(d): 32 | return d 33 | 34 | IPOPT_LIB = get_ipopt_lib() 35 | if IPOPT_LIB is None: 36 | raise Exception('failed to find ipopt lib') 37 | 38 | IPOPT_INC = os.path.join(IPOPT_DIR, 'include/coin/') 39 | 40 | FILES = ['src/callback.c', 'src/pyipoptcoremodule.c'] 41 | 42 | # The extra_link_args is commented out here; 43 | # that line was causing my pyipopt install to not work. 44 | # Also I am using coinmumps instead of coinhsl. 45 | pyipopt_extension = Extension( 46 | 'pyipoptcore', 47 | FILES, 48 | #extra_link_args=['-Wl,--rpath','-Wl,'+ IPOPT_LIB], 49 | library_dirs=[IPOPT_LIB], 50 | libraries=[ 51 | 'ipopt', 'coinblas', 52 | #'coinhsl', 53 | 'coinmumps', 54 | 'coinmetis', 55 | 'coinlapack','dl','m', 56 | ], 57 | include_dirs=[numpy_include, IPOPT_INC], 58 | ) 59 | 60 | setup( 61 | name="pyipopt", 62 | version="0.8", 63 | description="An IPOPT connector for Python", 64 | author="Eric Xu", 65 | author_email="xu.mathena@gmail.com", 66 | url="https://github.com/xuy/pyipopt", 67 | packages=['pyipopt'], 68 | package_dir={'pyipopt' : 'pyipoptpackage'}, 69 | ext_package='pyipopt', 70 | ext_modules=[pyipopt_extension], 71 | ) 72 | 73 | -------------------------------------------------------------------------------- /src/callback.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2008, Eric You Xu, Washington University All rights 3 | * reserved. Redistribution and use in source and binary forms, with or 4 | * without modification, are permitted provided that the following conditions 5 | * are met: 6 | * 7 | * Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. * Redistributions in 9 | * binary form must reproduce the above copyright notice, this list of 10 | * conditions and the following disclaimer in the documentation and/or other 11 | * materials provided with the distribution. * Neither the name of the 12 | * Washington University nor the names of its contributors may be used to 13 | * endorse or promote products derived from this software without specific 14 | * prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND ANY 17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR 20 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | * SUCH DAMAGE. 27 | */ 28 | 29 | /* 30 | * Added "eval_intermediate_callback" by 31 | * OpenMDAO at NASA Glenn Research Center, 2010 and 2011 32 | * 33 | * Changed logger from code contributed by alanfalloon 34 | */ 35 | 36 | #include "hook.h" 37 | #include 38 | 39 | void logger(const char *fmt, ...) 40 | { 41 | if (user_log_level == VERBOSE) { 42 | va_list ap; 43 | va_start(ap, fmt); 44 | PySys_WriteStdout(fmt, ap); 45 | va_end(ap); 46 | PySys_WriteStdout("\n"); 47 | } 48 | } 49 | 50 | Bool eval_intermediate_callback(Index alg_mod, /* 0 is regular, 1 is resto */ 51 | Index iter_count, Number obj_value, 52 | Number inf_pr, Number inf_du, 53 | Number mu, Number d_norm, 54 | Number regularization_size, 55 | Number alpha_du, Number alpha_pr, 56 | Index ls_trials, UserDataPtr data) 57 | { 58 | //logger("[Callback:E]intermediate_callback"); 59 | 60 | DispatchData *myowndata = (DispatchData *) data; 61 | UserDataPtr user_data = (UserDataPtr) myowndata->userdata; 62 | 63 | long result_as_long; 64 | Bool result_as_bool; 65 | 66 | PyObject *python_algmod = Py_BuildValue("i", alg_mod); 67 | PyObject *python_iter_count = Py_BuildValue("i", iter_count); 68 | PyObject *python_obj_value = Py_BuildValue("d", obj_value); 69 | PyObject *python_inf_pr = Py_BuildValue("d", inf_pr); 70 | PyObject *python_inf_du = Py_BuildValue("d", inf_du); 71 | PyObject *python_mu = Py_BuildValue("d", mu); 72 | PyObject *python_d_norm = Py_BuildValue("d", d_norm); 73 | PyObject *python_regularization_size = 74 | Py_BuildValue("d", regularization_size); 75 | PyObject *python_alpha_du = Py_BuildValue("d", alpha_du); 76 | PyObject *python_alpha_pr = Py_BuildValue("d", alpha_pr); 77 | PyObject *python_ls_trials = Py_BuildValue("i", ls_trials); 78 | 79 | PyObject *arglist = NULL; 80 | 81 | if (user_data != NULL) 82 | arglist = Py_BuildValue("(OOOOOOOOOOOO)", 83 | python_algmod, 84 | python_iter_count, 85 | python_obj_value, 86 | python_inf_pr, 87 | python_inf_du, 88 | python_mu, 89 | python_d_norm, 90 | python_regularization_size, 91 | python_alpha_du, 92 | python_alpha_pr, 93 | python_ls_trials, 94 | (PyObject *) user_data); 95 | else 96 | arglist = Py_BuildValue("(OOOOOOOOOOO)", 97 | python_algmod, 98 | python_iter_count, 99 | python_obj_value, 100 | python_inf_pr, 101 | python_inf_du, 102 | python_mu, 103 | python_d_norm, 104 | python_regularization_size, 105 | python_alpha_du, 106 | python_alpha_pr, python_ls_trials); 107 | 108 | PyObject *result = 109 | PyObject_CallObject(myowndata->eval_intermediate_callback_python, 110 | arglist); 111 | 112 | if (!result) 113 | PyErr_Print(); 114 | 115 | result_as_long = PyLong_AsLong(result); 116 | result_as_bool = (Bool) result_as_long; 117 | 118 | Py_DECREF(result); 119 | Py_CLEAR(arglist); 120 | //logger("[Callback:R] intermediate_callback"); 121 | return result_as_bool; 122 | } 123 | 124 | Bool 125 | eval_f(Index n, Number * x, Bool new_x, Number * obj_value, UserDataPtr data) 126 | { 127 | //logger("[Callback:E] eval_f"); 128 | 129 | npy_intp dims[1]; 130 | dims[0] = n; 131 | 132 | DispatchData *myowndata = (DispatchData *) data; 133 | UserDataPtr user_data = (UserDataPtr) myowndata->userdata; 134 | 135 | // import_array (); 136 | 137 | import_array1(FALSE); 138 | PyObject *arrayx = 139 | PyArray_SimpleNewFromData(1, dims, PyArray_DOUBLE, (char *)x); 140 | if (!arrayx) 141 | return FALSE; 142 | 143 | if (new_x && myowndata->apply_new_python) { 144 | /* Call the python function to applynew */ 145 | PyObject *arg1; 146 | arg1 = Py_BuildValue("(O)", arrayx); 147 | PyObject *tempresult = PyObject_CallObject( 148 | myowndata->apply_new_python, arg1); 149 | if (tempresult == NULL) { 150 | logger("[Error] Python function apply_new returns NULL"); 151 | PyErr_Print(); 152 | Py_DECREF(arg1); 153 | return FALSE; 154 | } 155 | Py_DECREF(arg1); 156 | Py_DECREF(tempresult); 157 | } 158 | 159 | PyObject *arglist; 160 | if (user_data != NULL) { 161 | arglist = Py_BuildValue("(OO)", arrayx, (PyObject *) user_data); 162 | } else { 163 | arglist = Py_BuildValue("(O)", arrayx); 164 | } 165 | 166 | PyObject *result = PyObject_CallObject(myowndata->eval_f_python, arglist); 167 | 168 | if (result == NULL) { 169 | logger("[Error] Python function eval_f returns NULL"); 170 | PyErr_Print(); 171 | Py_DECREF(arrayx); 172 | Py_CLEAR(arglist); 173 | return FALSE; 174 | } 175 | 176 | *obj_value = PyFloat_AsDouble(result); 177 | 178 | if (PyErr_Occurred()) { 179 | logger("[Error] Python function eval_f returns non-PyFloat"); 180 | PyErr_Print(); 181 | Py_DECREF(result); 182 | Py_DECREF(arrayx); 183 | Py_CLEAR(arglist); 184 | return FALSE; 185 | } 186 | 187 | Py_DECREF(result); 188 | Py_DECREF(arrayx); 189 | Py_CLEAR(arglist); 190 | //logger("[Callback:R] eval_f"); 191 | return TRUE; 192 | } 193 | 194 | Bool 195 | eval_grad_f(Index n, Number * x, Bool new_x, Number * grad_f, UserDataPtr data) 196 | { 197 | //logger("[Callback:E] eval_grad_f"); 198 | 199 | DispatchData *myowndata = (DispatchData *) data; 200 | UserDataPtr user_data = (UserDataPtr) myowndata->userdata; 201 | 202 | if (myowndata->eval_grad_f_python == NULL) 203 | PyErr_Print(); 204 | 205 | /* int dims[1]; */ 206 | npy_intp dims[1]; 207 | dims[0] = n; 208 | // import_array (); 209 | 210 | import_array1(FALSE); 211 | 212 | /* 213 | * PyObject *arrayx = PyArray_FromDimsAndData(1, dims, PyArray_DOUBLE 214 | * , (char*) x); 215 | */ 216 | PyObject *arrayx = 217 | PyArray_SimpleNewFromData(1, dims, PyArray_DOUBLE, (char *)x); 218 | if (!arrayx) 219 | return FALSE; 220 | 221 | if (new_x && myowndata->apply_new_python) { 222 | /* Call the python function to applynew */ 223 | PyObject *arg1 = Py_BuildValue("(O)", arrayx); 224 | PyObject *tempresult = PyObject_CallObject( 225 | myowndata->apply_new_python, arg1); 226 | if (tempresult == NULL) { 227 | logger("[Error] Python function apply_new returns NULL"); 228 | PyErr_Print(); 229 | Py_DECREF(arg1); 230 | return FALSE; 231 | } 232 | Py_DECREF(arg1); 233 | Py_DECREF(tempresult); 234 | } 235 | 236 | PyObject *arglist; 237 | if (user_data != NULL) 238 | arglist = Py_BuildValue("(OO)", arrayx, (PyObject *) user_data); 239 | else 240 | arglist = Py_BuildValue("(O)", arrayx); 241 | 242 | PyArrayObject *result = (PyArrayObject *) PyObject_CallObject( 243 | myowndata->eval_grad_f_python, arglist); 244 | 245 | if (result == NULL) { 246 | logger("[Error] Python function eval_grad_f returns NULL"); 247 | PyErr_Print(); 248 | return FALSE; 249 | } 250 | 251 | if (!PyArray_Check(result)) { 252 | logger("[Error] Python function eval_grad_f returns non-PyArray"); 253 | Py_DECREF(result); 254 | return FALSE; 255 | } 256 | 257 | double *tempdata = (double *)result->data; 258 | int i; 259 | for (i = 0; i < n; i++) 260 | grad_f[i] = tempdata[i]; 261 | 262 | Py_DECREF(result); 263 | Py_CLEAR(arrayx); 264 | Py_CLEAR(arglist); 265 | //logger("[Callback:R] eval_grad_f"); 266 | return TRUE; 267 | } 268 | 269 | Bool 270 | eval_g(Index n, Number * x, Bool new_x, Index m, Number * g, UserDataPtr data) 271 | { 272 | 273 | //logger("[Callback:E] eval_g"); 274 | 275 | DispatchData *myowndata = (DispatchData *) data; 276 | UserDataPtr user_data = (UserDataPtr) myowndata->userdata; 277 | 278 | if (myowndata->eval_g_python == NULL) 279 | PyErr_Print(); 280 | /* int dims[1]; */ 281 | npy_intp dims[1]; 282 | int i; 283 | double *tempdata; 284 | 285 | dims[0] = n; 286 | // import_array (); 287 | 288 | import_array1(FALSE); 289 | 290 | /* 291 | * PyObject *arrayx = PyArray_FromDimsAndData(1, dims, PyArray_DOUBLE 292 | * , (char*) x); 293 | */ 294 | PyObject *arrayx = 295 | PyArray_SimpleNewFromData(1, dims, PyArray_DOUBLE, (char *)x); 296 | if (!arrayx) 297 | return FALSE; 298 | 299 | if (new_x && myowndata->apply_new_python) { 300 | /* Call the python function to applynew */ 301 | PyObject *arg1 = Py_BuildValue("(O)", arrayx); 302 | PyObject *tempresult = PyObject_CallObject( 303 | myowndata->apply_new_python, arg1); 304 | if (tempresult == NULL) { 305 | logger("[Error] Python function apply_new returns NULL"); 306 | PyErr_Print(); 307 | Py_DECREF(arg1); 308 | return FALSE; 309 | } 310 | Py_DECREF(arg1); 311 | Py_DECREF(tempresult); 312 | } 313 | 314 | PyObject *arglist; 315 | if (user_data != NULL) 316 | arglist = Py_BuildValue("(OO)", arrayx, (PyObject *) user_data); 317 | else 318 | arglist = Py_BuildValue("(O)", arrayx); 319 | 320 | PyArrayObject *result = (PyArrayObject *) PyObject_CallObject( 321 | myowndata->eval_g_python, arglist); 322 | 323 | if (result == NULL) { 324 | logger("[Error] Python function eval_g returns NULL"); 325 | PyErr_Print(); 326 | return FALSE; 327 | } 328 | 329 | if (!PyArray_Check(result)) { 330 | logger("[Error] Python function eval_g returns non-PyArray"); 331 | Py_DECREF(result); 332 | return FALSE; 333 | } 334 | 335 | tempdata = (double *)result->data; 336 | for (i = 0; i < m; i++) { 337 | g[i] = tempdata[i]; 338 | } 339 | 340 | Py_DECREF(result); 341 | Py_CLEAR(arrayx); 342 | Py_CLEAR(arglist); 343 | //logger("[Callback:R] eval_g"); 344 | return TRUE; 345 | } 346 | 347 | Bool 348 | eval_jac_g(Index n, Number * x, Bool new_x, 349 | Index m, Index nele_jac, 350 | Index * iRow, Index * jCol, Number * values, UserDataPtr data) 351 | { 352 | 353 | //logger("[Callback:E] eval_jac_g"); 354 | 355 | DispatchData *myowndata = (DispatchData *) data; 356 | UserDataPtr user_data = (UserDataPtr) myowndata->userdata; 357 | 358 | int i; 359 | long *rowd = NULL; 360 | long *cold = NULL; 361 | 362 | /* int dims[1]; */ 363 | npy_intp dims[1]; 364 | dims[0] = n; 365 | 366 | double *tempdata; 367 | 368 | if (myowndata->eval_grad_f_python == NULL) /* Why??? */ 369 | PyErr_Print(); 370 | 371 | if (values == NULL) { 372 | /* import_array (); */ 373 | import_array1(FALSE); 374 | 375 | PyObject *arrayx = 376 | PyArray_SimpleNewFromData(1, dims, PyArray_DOUBLE, 377 | (char *)x); 378 | if (!arrayx) 379 | return FALSE; 380 | 381 | PyObject *arglist; 382 | 383 | if (user_data != NULL) 384 | arglist = Py_BuildValue("(OOO)", 385 | arrayx, Py_True, 386 | (PyObject *) user_data); 387 | else 388 | arglist = Py_BuildValue("(OO)", arrayx, Py_True); 389 | 390 | PyObject *result = 391 | PyObject_CallObject(myowndata->eval_jac_g_python, arglist); 392 | if (!result) { 393 | 394 | logger("[PyIPOPT] return from eval_jac_g is null\n"); 395 | /* TODO: need to deal with reference counting here */ 396 | return FALSE; 397 | } 398 | if (!PyTuple_Check(result)) { 399 | PyErr_Print(); 400 | } 401 | PyArrayObject *row = 402 | (PyArrayObject *) PyTuple_GetItem(result, 0); 403 | PyArrayObject *col = 404 | (PyArrayObject *) PyTuple_GetItem(result, 1); 405 | 406 | if (!row || !col || !PyArray_Check(row) || !PyArray_Check(col)) { 407 | logger 408 | ("[Error] there are problems with row or col in eval_jac_g.\n"); 409 | PyErr_Print(); 410 | } 411 | rowd = (long *)row->data; 412 | cold = (long *)col->data; 413 | 414 | for (i = 0; i < nele_jac; i++) { 415 | iRow[i] = (Index) rowd[i]; 416 | jCol[i] = (Index) cold[i]; 417 | } 418 | Py_CLEAR(arrayx); 419 | Py_DECREF(result); 420 | Py_CLEAR(arglist); 421 | //logger("[Callback:R] eval_jac_g(1)"); 422 | } else { 423 | PyObject *arrayx = 424 | PyArray_SimpleNewFromData(1, dims, PyArray_DOUBLE, 425 | (char *)x); 426 | 427 | if (!arrayx) 428 | return FALSE; 429 | 430 | if (new_x && myowndata->apply_new_python) { 431 | /* Call the python function to applynew */ 432 | PyObject *arg1 = Py_BuildValue("(O)", arrayx); 433 | PyObject *tempresult = 434 | PyObject_CallObject(myowndata->apply_new_python, 435 | arg1); 436 | if (tempresult == NULL) { 437 | logger("[Error] Python function apply_new returns NULL"); 438 | Py_DECREF(arg1); 439 | return FALSE; 440 | } 441 | Py_DECREF(arg1); 442 | Py_DECREF(tempresult); 443 | } 444 | PyObject *arglist; 445 | if (user_data != NULL) 446 | arglist = Py_BuildValue("(OOO)", 447 | arrayx, Py_False, 448 | (PyObject *) user_data); 449 | else 450 | arglist = Py_BuildValue("(OO)", arrayx, Py_False); 451 | 452 | PyArrayObject *result = (PyArrayObject *) PyObject_CallObject( 453 | myowndata->eval_jac_g_python, arglist); 454 | 455 | if (result == NULL) { 456 | logger("[Error] Python function eval_jac_g returns NULL"); 457 | PyErr_Print(); 458 | return FALSE; 459 | } 460 | 461 | if (!PyArray_Check(result)) { 462 | logger("[Error] Python function eval_jac_g returns non-PyArray"); 463 | Py_DECREF(result); 464 | return FALSE; 465 | } 466 | 467 | /* 468 | * Code is buggy here. We assume that result is a double 469 | * array 470 | */ 471 | assert(result->descr->type == 'd'); 472 | tempdata = (double *)result->data; 473 | 474 | for (i = 0; i < nele_jac; i++) 475 | values[i] = tempdata[i]; 476 | 477 | Py_DECREF(result); 478 | Py_CLEAR(arrayx); 479 | Py_CLEAR(arglist); 480 | //logger("[Callback:R] eval_jac_g(2)"); 481 | } 482 | //logger("[Callback:R] eval_jac_g"); 483 | return TRUE; 484 | } 485 | 486 | Bool 487 | eval_h(Index n, Number * x, Bool new_x, Number obj_factor, 488 | Index m, Number * lambda, Bool new_lambda, 489 | Index nele_hess, Index * iRow, Index * jCol, 490 | Number * values, UserDataPtr data) 491 | { 492 | //logger("[Callback:E] eval_h"); 493 | 494 | DispatchData *myowndata = (DispatchData *) data; 495 | UserDataPtr user_data = (UserDataPtr) myowndata->userdata; 496 | 497 | int i; 498 | npy_intp dims[1]; 499 | npy_intp dims2[1]; 500 | 501 | if (myowndata->eval_h_python == NULL) { 502 | logger("[Error] There is no eval_h assigned"); 503 | return FALSE; 504 | } 505 | if (values == NULL) { 506 | //logger("[Callback:E] eval_h (1a)"); 507 | PyObject *newx = Py_True; 508 | PyObject *objfactor = Py_BuildValue("d", obj_factor); 509 | PyObject *lagrange = Py_True; 510 | 511 | PyObject *arglist; 512 | 513 | if (user_data != NULL) { 514 | arglist = Py_BuildValue( 515 | "(OOOOO)", newx, lagrange, objfactor, Py_True, 516 | (PyObject *) user_data); 517 | } else { 518 | arglist = Py_BuildValue( 519 | "(OOOO)", newx, lagrange, objfactor, Py_True); 520 | } 521 | 522 | if (arglist == NULL) { 523 | logger("[Error] failed to build arglist for eval_h"); 524 | PyErr_Print(); 525 | return FALSE; 526 | } else { 527 | logger("[Logspam] built arglist for eval_h"); 528 | } 529 | 530 | PyObject *result = PyObject_CallObject(myowndata->eval_h_python, arglist); 531 | 532 | if (result == NULL) { 533 | logger("[Error] Python function eval_h returns NULL"); 534 | PyErr_Print(); 535 | return FALSE; 536 | } else { 537 | logger("[Logspam] Python function eval_h returns non-NULL"); 538 | } 539 | 540 | int result_size = PyTuple_Size(result); 541 | 542 | if (result_size == -1) { 543 | logger("[Error] Python function eval_h returns non-PyTuple"); 544 | Py_DECREF(result); 545 | return FALSE; 546 | } 547 | 548 | if (result_size != 2) { 549 | logger("[Error] Python function eval_h returns a tuple whose len != 2"); 550 | Py_DECREF(result); 551 | return FALSE; 552 | } 553 | 554 | //logger("[Callback:E] eval_h (tuple is the right length)"); 555 | 556 | PyArrayObject *row = (PyArrayObject *) PyTuple_GetItem(result, 0); 557 | PyArrayObject *col = (PyArrayObject *) PyTuple_GetItem(result, 1); 558 | 559 | long *rdata = (long *)row->data; 560 | long *cdata = (long *)col->data; 561 | 562 | for (i = 0; i < nele_hess; i++) { 563 | iRow[i] = (Index) rdata[i]; 564 | jCol[i] = (Index) cdata[i]; 565 | /* 566 | * logger("PyIPOPT_DEBUG %d, %d\n", iRow[i], 567 | * jCol[i]); 568 | */ 569 | } 570 | 571 | //logger("[Callback:E] eval_h (clearing stuff now)"); 572 | 573 | Py_DECREF(objfactor); 574 | Py_DECREF(result); 575 | Py_CLEAR(arglist); 576 | //logger("[Callback:R] eval_h (1b)"); 577 | } else { 578 | //logger("[Callback:R] eval_h (2a)"); 579 | 580 | PyObject *objfactor = Py_BuildValue("d", obj_factor); 581 | 582 | dims[0] = n; 583 | PyObject *arrayx = 584 | PyArray_SimpleNewFromData(1, dims, PyArray_DOUBLE, 585 | (char *)x); 586 | if (!arrayx) 587 | return FALSE; 588 | 589 | if (new_x && myowndata->apply_new_python) { 590 | /* Call the python function to applynew */ 591 | PyObject *arg1 = Py_BuildValue("(O)", arrayx); 592 | PyObject *tempresult = PyObject_CallObject( 593 | myowndata->apply_new_python, arg1); 594 | if (tempresult == NULL) { 595 | logger("[Error] Python function apply_new returns NULL"); 596 | PyErr_Print(); 597 | Py_DECREF(arg1); 598 | return FALSE; 599 | } 600 | Py_DECREF(arg1); 601 | Py_DECREF(tempresult); 602 | } 603 | dims2[0] = m; 604 | PyObject *lagrangex = PyArray_SimpleNewFromData( 605 | 1, dims2, PyArray_DOUBLE, (char *)lambda); 606 | if (!lagrangex) 607 | return FALSE; 608 | 609 | PyObject *arglist; 610 | 611 | if (user_data != NULL) { 612 | arglist = Py_BuildValue( 613 | "(OOOOO)", arrayx, lagrangex, objfactor, Py_False, 614 | (PyObject *) user_data); 615 | } else { 616 | arglist = Py_BuildValue( 617 | "(OOOO)", arrayx, lagrangex, objfactor, Py_False); 618 | } 619 | PyArrayObject *result = (PyArrayObject *) PyObject_CallObject( 620 | myowndata->eval_h_python, arglist); 621 | 622 | if (result == NULL) { 623 | logger("[Error] Python function eval_h returns NULL"); 624 | PyErr_Print(); 625 | return FALSE; 626 | } 627 | 628 | if (!PyArray_Check(result)) { 629 | logger("[Error] Python function eval_h returns non-PyArray"); 630 | Py_DECREF(result); 631 | return FALSE; 632 | } 633 | 634 | double *tempdata = (double *)result->data; 635 | for (i = 0; i < nele_hess; i++) { 636 | values[i] = tempdata[i]; 637 | } 638 | Py_CLEAR(arrayx); 639 | Py_CLEAR(lagrangex); 640 | Py_CLEAR(objfactor); 641 | Py_DECREF(result); 642 | Py_CLEAR(arglist); 643 | //logger("[Callback:R] eval_h (2b)"); 644 | } 645 | return TRUE; 646 | } 647 | -------------------------------------------------------------------------------- /src/hook.h: -------------------------------------------------------------------------------- 1 | // Author: Eric Xu 2 | // Licensed under BSD 3 | 4 | #include "Python.h" 5 | #include "IpStdCInterface.h" 6 | #include 7 | #include "numpy/arrayobject.h" 8 | 9 | #ifndef PY_IPOPT_HOOK_ 10 | #define PY_IPOPT_HOOK_ 11 | 12 | // A series of callback functions used by Ipopt C Interface 13 | Bool eval_f(Index n, 14 | Number * x, Bool new_x, Number * obj_value, UserDataPtr user_data); 15 | 16 | Bool eval_grad_f(Index n, 17 | Number * x, 18 | Bool new_x, Number * grad_f, UserDataPtr user_data); 19 | 20 | Bool eval_g(Index n, 21 | Number * x, Bool new_x, Index m, Number * g, UserDataPtr user_data); 22 | 23 | Bool eval_jac_g(Index n, Number * x, Bool new_x, 24 | Index m, Index nele_jac, 25 | Index * iRow, Index * jCol, Number * values, 26 | UserDataPtr user_data); 27 | 28 | Bool eval_h(Index n, Number * x, Bool new_x, Number obj_factor, 29 | Index m, Number * lambda, Bool new_lambda, 30 | Index nele_hess, Index * iRow, Index * jCol, 31 | Number * values, UserDataPtr user_data); 32 | 33 | Bool eval_intermediate_callback(Index alg_mod, 34 | Index iter_count, Number obj_value, 35 | Number inf_pr, Number inf_du, 36 | Number mu, Number d_norm, 37 | Number regularization_size, 38 | Number alpha_du, Number alpha_pr, 39 | Index ls_trials, UserDataPtr data); 40 | 41 | typedef struct { 42 | PyObject *eval_f_python; 43 | PyObject *eval_grad_f_python; 44 | PyObject *eval_g_python; 45 | PyObject *eval_jac_g_python; 46 | PyObject *eval_h_python; 47 | PyObject *apply_new_python; 48 | PyObject *eval_intermediate_callback_python; 49 | PyObject *userdata; 50 | } DispatchData; 51 | 52 | 53 | #if PY_MAJOR_VERSION < 3 54 | PyObject *problem_getattr(PyObject * self, char *attrname); 55 | #endif 56 | 57 | /* Logging */ 58 | #define VERBOSE 2 59 | #define IPOPT_OUTPUT 1 60 | #define TERSE 0 61 | extern int user_log_level; 62 | void logger(const char *fmt, ...); 63 | 64 | typedef struct { 65 | PyObject_HEAD IpoptProblem nlp; 66 | DispatchData *data; 67 | Index n_variables; 68 | Index m_constraints; 69 | } problem; 70 | 71 | #endif // PY_IPOPT_HOOK_ 72 | -------------------------------------------------------------------------------- /src/pyipoptcoremodule.c: -------------------------------------------------------------------------------- 1 | /* Author: Eric Xu */ 2 | /* Licensed under BSD */ 3 | /* */ 4 | /* Modifications on logger made by */ 5 | /* OpenMDAO at NASA Glenn Research Center, 2010 and 2011 */ 6 | /* Modifications on the SAFE_FREE macro made by */ 7 | /* Guillaume Jacquenot, 2012 */ 8 | 9 | #include "hook.h" 10 | 11 | #ifndef SAFE_FREE 12 | #define SAFE_FREE(p) {if (p) {free(p); (p)= NULL;}} 13 | #endif 14 | 15 | /* 16 | * Let's put the static char docs at the beginning of this file... 17 | */ 18 | 19 | static char PYIPOPT_SOLVE_DOC[] = "solve(x) -> (x, ml, mu, obj)\n \ 20 | \n \ 21 | Call Ipopt to solve problem created before and return \n \ 22 | a tuple that contains final solution x, upper and lower\n \ 23 | bound for multiplier, final objective function obj, \n \ 24 | and the return status of ipopt. \n"; 25 | 26 | static char PYIPOPT_SET_INTERMEDIATE_CALLBACK_DOC[] = 27 | "set_intermediate_callback(callback_function)\n \ 28 | \n \ 29 | Set the intermediate callback function. \ 30 | This gets called each iteration."; 31 | 32 | static char PYIPOPT_CLOSE_DOC[] = "After all the solving, close the model\n"; 33 | 34 | static char PYIPOPT_ADD_STR_OPTION_DOC[] = 35 | "Set the String (char* in C) option for Ipopt. Refer to the Ipopt \n \ 36 | document for more information about Ipopt options, or use \n \ 37 | ipopt --print-options \n \ 38 | to see a list of available options."; 39 | 40 | static char PYIPOPT_ADD_INT_OPTION_DOC[] = 41 | "Set the Int (int in C) option for Ipopt. Refer to the Ipopt \n \ 42 | document for more information about Ipopt options, or use \n \ 43 | ipopt --print-options \n \ 44 | to see a list of available options."; 45 | 46 | static char PYIPOPT_ADD_NUM_OPTION_DOC[] = 47 | "Set the Number (double in C) option for Ipopt. Refer to the Ipopt \n \ 48 | document for more information about Ipopt options, or use \n \ 49 | ipopt --print-options \n \ 50 | to see a list of available options."; 51 | 52 | static char PYIPOPT_CREATE_DOC[] = 53 | "create(n, xl, xu, m, gl, gu, nnzj, nnzh, eval_f, eval_grad_f, eval_g, eval_jac_g) -> Boolean\n \ 54 | \n \ 55 | Create a problem instance and return True if succeed \n \ 56 | \n \ 57 | n is the number of variables, \n \ 58 | xl is the lower bound of x as bounded constraints \n \ 59 | xu is the upper bound of x as bounded constraints \n \ 60 | both xl, xu should be one dimension arrays with length n \n \ 61 | \n \ 62 | m is the number of constraints, \n \ 63 | gl is the lower bound of constraints \n \ 64 | gu is the upper bound of constraints \n \ 65 | both gl, gu should be one dimension arrays with length m \n \ 66 | nnzj is the number of nonzeros in Jacobi matrix \n \ 67 | nnzh is the number of non-zeros in Hessian matrix, you can set it to 0 \n \ 68 | \n \ 69 | eval_f is the call back function to calculate objective value, \n \ 70 | it takes one single argument x as input vector \n \ 71 | eval_grad_f calculates gradient for objective function \n \ 72 | eval_g calculates the constraint values and return an array \n \ 73 | eval_jac_g calculates the Jacobi matrix. It takes two arguments, \n \ 74 | the first is the variable x and the second is a Boolean flag \n \ 75 | if the flag is true, it supposed to return a tuple (row, col) \n \ 76 | to indicate the sparse Jacobi matrix's structure. \n \ 77 | if the flag is false if returns the values of the Jacobi matrix \n \ 78 | with length nnzj \n \ 79 | eval_h calculates the hessian matrix, it's optional. \n \ 80 | if omitted, please set nnzh to 0 and Ipopt will use approximated hessian \n \ 81 | which will make the convergence slower. "; 82 | 83 | static char PYIPOPT_LOG_DOC[] = "set_loglevel(level)\n \ 84 | \n \ 85 | Set the log level of PyIPOPT \n \ 86 | levels: \n \ 87 | 0: Terse, no log from pyipopt \n \ 88 | 1: Moderate, logs for ipopt \n \ 89 | 2: Verbose, logs for both ipopt and pyipopt. \n"; 90 | 91 | 92 | 93 | int user_log_level = TERSE; 94 | 95 | /* Object Section */ 96 | /* sig of this is void foo(PyO*) */ 97 | static void problem_dealloc(PyObject * self) 98 | { 99 | problem *temp = (problem *) self; 100 | SAFE_FREE(temp->data); 101 | Py_TYPE(self)->tp_free((PyObject*)self); 102 | } 103 | 104 | PyObject *solve(PyObject * self, PyObject * args); 105 | PyObject *set_intermediate_callback(PyObject * self, PyObject * args); 106 | PyObject *close_model(PyObject * self, PyObject * args); 107 | 108 | static PyObject *add_str_option(PyObject * self, PyObject * args) 109 | { 110 | problem *temp = (problem *) self; 111 | IpoptProblem nlp = (IpoptProblem) (temp->nlp); 112 | char *param; 113 | char *value; 114 | Bool ret; 115 | 116 | if (!PyArg_ParseTuple(args, "ss:str_option", ¶m, &value)) { 117 | return NULL; 118 | } 119 | ret = AddIpoptStrOption(nlp, (char *)param, value); 120 | if (ret) { 121 | Py_INCREF(Py_True); 122 | return Py_True; 123 | } else { 124 | return PyErr_Format(PyExc_ValueError, 125 | "%s is not a valid string option", param); 126 | } 127 | } 128 | 129 | static PyObject *add_int_option(PyObject * self, PyObject * args) 130 | { 131 | 132 | problem *temp = (problem *) self; 133 | IpoptProblem nlp = (IpoptProblem) (temp->nlp); 134 | 135 | char *param; 136 | int value; 137 | 138 | Bool ret; 139 | 140 | if (!PyArg_ParseTuple(args, "si:int_option", ¶m, &value)) { 141 | return NULL; 142 | } 143 | ret = AddIpoptIntOption(nlp, (char *)param, value); 144 | if (ret) { 145 | Py_INCREF(Py_True); 146 | return Py_True; 147 | } else { 148 | return PyErr_Format(PyExc_ValueError, 149 | "%s is not a valid int option", param); 150 | } 151 | } 152 | 153 | static PyObject *add_num_option(PyObject * self, PyObject * args) 154 | { 155 | problem *temp = (problem *) self; 156 | IpoptProblem nlp = (IpoptProblem) (temp->nlp); 157 | 158 | char *param; 159 | double value; 160 | 161 | Bool ret; 162 | 163 | if (!PyArg_ParseTuple(args, "sd:num_option", ¶m, &value)) { 164 | return NULL; 165 | } 166 | ret = AddIpoptNumOption(nlp, (char *)param, value); 167 | if (ret) { 168 | Py_INCREF(Py_True); 169 | return Py_True; 170 | } else { 171 | return PyErr_Format(PyExc_ValueError, 172 | "%s is not a valid num option", param); 173 | } 174 | } 175 | 176 | PyMethodDef problem_methods[] = { 177 | {"solve", solve, METH_VARARGS, PYIPOPT_SOLVE_DOC} 178 | , 179 | {"set_intermediate_callback", set_intermediate_callback, METH_VARARGS, 180 | PYIPOPT_SET_INTERMEDIATE_CALLBACK_DOC} 181 | , 182 | {"close", close_model, METH_VARARGS, PYIPOPT_CLOSE_DOC} 183 | , 184 | {"int_option", add_int_option, METH_VARARGS, PYIPOPT_ADD_INT_OPTION_DOC} 185 | , 186 | {"str_option", add_str_option, METH_VARARGS, PYIPOPT_ADD_STR_OPTION_DOC} 187 | , 188 | {"num_option", add_num_option, METH_VARARGS, PYIPOPT_ADD_NUM_OPTION_DOC} 189 | , 190 | {NULL, NULL} 191 | , 192 | }; 193 | 194 | #if PY_MAJOR_VERSION < 3 195 | PyObject *problem_getattr(PyObject * self, char *attrname) 196 | { 197 | PyObject *result = NULL; 198 | result = Py_FindMethod(problem_methods, self, attrname); 199 | return result; 200 | } 201 | 202 | 203 | /* 204 | * had to replace PyObject_HEAD_INIT(&PyType_Type) in order to get this to 205 | * compile on Windows 206 | */ 207 | PyTypeObject IpoptProblemType = { 208 | PyObject_HEAD_INIT(NULL) 209 | 0, /* ob_size */ 210 | "pyipoptcore.Problem", /* tp_name */ 211 | sizeof(problem), /* tp_basicsize */ 212 | 0, /* tp_itemsize */ 213 | problem_dealloc, /* tp_dealloc */ 214 | 0, /* tp_print */ 215 | problem_getattr, /* tp_getattr */ 216 | 0, /* tp_setattr */ 217 | 0, /* tp_compare */ 218 | 0, /* tp_repr */ 219 | 0, /* tp_as_number */ 220 | 0, /* tp_as_sequence */ 221 | 0, /* tp_as_mapping */ 222 | 0, /* tp_hash */ 223 | 0, /* tp_call */ 224 | 0, /* tp_str */ 225 | 0, /* tp_getattro */ 226 | 0, /* tp_setattro */ 227 | 0, /* tp_as_buffer */ 228 | Py_TPFLAGS_DEFAULT, /* tp_flags */ 229 | "The IPOPT problem object in python", /* tp_doc */ 230 | }; 231 | 232 | #else 233 | 234 | PyDoc_STRVAR(IpoptProblemType__doc__, "The IPOPT problem object in python"); 235 | 236 | PyTypeObject IpoptProblemType = { 237 | PyVarObject_HEAD_INIT(NULL, 0) 238 | "pyipoptcore.Problem", /* tp_name */ 239 | sizeof(problem), /*tp_basicsize*/ 240 | 0, /*tp_itemsize*/ 241 | /* methods */ 242 | (destructor)problem_dealloc, /*tp_dealloc*/ 243 | (printfunc)0, /*tp_print*/ 244 | 0, /*tp_getattr*/ 245 | 0, /*tp_setattr*/ 246 | 0, /*tp_reserved*/ 247 | (reprfunc)0, /*tp_repr*/ 248 | 0, /*tp_as_number*/ 249 | 0, /*tp_as_sequence*/ 250 | 0, /*tp_as_mapping*/ 251 | (hashfunc)0, /*tp_hash*/ 252 | (ternaryfunc)0, /*tp_call*/ 253 | (reprfunc)0, /*tp_str*/ 254 | (getattrofunc)0, /* tp_getattro */ 255 | (setattrofunc)0, /* tp_setattro */ 256 | 0, /* tp_as_buffer */ 257 | Py_TPFLAGS_DEFAULT, /*tp_flags*/ 258 | IpoptProblemType__doc__, /* tp_doc - Documentation string */ 259 | (traverseproc)0, /* tp_traverse */ 260 | (inquiry)0, /* tp_clear */ 261 | 0, /* tp_richcompare */ 262 | 0, /* tp_weaklistoffset */ 263 | 0, /* tp_iter */ 264 | 0, /* tp_iternext */ 265 | problem_methods, /* tp_methods */ 266 | }; 267 | #endif 268 | 269 | /* 270 | * FIXME: use module or package constants for the log levels, 271 | * either in pyipoptcore or in the parent package. 272 | * They are currently #defined in a header file. 273 | */ 274 | static PyObject *set_loglevel(PyObject * obj, PyObject * args) 275 | { 276 | int l; 277 | if (!PyArg_ParseTuple(args, "i", &l)) { 278 | PySys_WriteStdout("l is %d \n", l); 279 | return NULL; 280 | } 281 | if (l < 0 || l > 2) { 282 | return NULL; 283 | } 284 | user_log_level = l; 285 | Py_INCREF(Py_True); 286 | return Py_True; 287 | } 288 | 289 | static PyObject *create(PyObject * obj, PyObject * args) 290 | { 291 | PyObject *f = NULL; 292 | PyObject *gradf = NULL; 293 | PyObject *g = NULL; 294 | PyObject *jacg = NULL; 295 | PyObject *h = NULL; 296 | PyObject *applynew = NULL; 297 | 298 | DispatchData myowndata; 299 | 300 | /* 301 | * I have to create a new python object here, return this python object 302 | */ 303 | 304 | int n; /* Number of variables */ 305 | PyArrayObject *xL = NULL; 306 | PyArrayObject *xU = NULL; 307 | int m; /* Number of constraints */ 308 | PyArrayObject *gL = NULL; 309 | PyArrayObject *gU = NULL; 310 | 311 | problem *object = NULL; 312 | 313 | int nele_jac; 314 | int nele_hess; 315 | 316 | Number *x_L = NULL; /* lower bounds on x */ 317 | Number *x_U = NULL; /* upper bounds on x */ 318 | Number *g_L = NULL; /* lower bounds on g */ 319 | Number *g_U = NULL; /* upper bounds on g */ 320 | 321 | double *xldata, *xudata; 322 | double *gldata, *gudata; 323 | 324 | int i; 325 | 326 | DispatchData *dp = NULL; 327 | 328 | PyObject *retval = NULL; 329 | 330 | /* Init the myowndata field */ 331 | myowndata.eval_f_python = NULL; 332 | myowndata.eval_grad_f_python = NULL; 333 | myowndata.eval_g_python = NULL; 334 | myowndata.eval_jac_g_python = NULL; 335 | myowndata.eval_h_python = NULL; 336 | myowndata.apply_new_python = NULL; 337 | myowndata.userdata = NULL; 338 | 339 | /* "O!", &PyArray_Type &a_x */ 340 | if (!PyArg_ParseTuple(args, "iO!O!iO!O!iiOOOO|OO:pyipoptcreate", 341 | &n, &PyArray_Type, &xL, 342 | &PyArray_Type, &xU, 343 | &m, 344 | &PyArray_Type, &gL, 345 | &PyArray_Type, &gU, 346 | &nele_jac, &nele_hess, 347 | &f, &gradf, &g, &jacg, &h, &applynew)) { 348 | retval = NULL; 349 | SAFE_FREE(x_L); 350 | SAFE_FREE(x_U); 351 | SAFE_FREE(g_L); 352 | SAFE_FREE(g_U); 353 | return retval; 354 | } 355 | if (!PyCallable_Check(f) || 356 | !PyCallable_Check(gradf) || 357 | !PyCallable_Check(g) || !PyCallable_Check(jacg)) { 358 | PyErr_SetString(PyExc_TypeError, 359 | "Need a callable object for callback functions"); 360 | retval = NULL; 361 | SAFE_FREE(x_L); 362 | SAFE_FREE(x_U); 363 | SAFE_FREE(g_L); 364 | SAFE_FREE(g_U); 365 | return retval; 366 | } 367 | myowndata.eval_f_python = f; 368 | myowndata.eval_grad_f_python = gradf; 369 | myowndata.eval_g_python = g; 370 | myowndata.eval_jac_g_python = jacg; 371 | 372 | if (h != NULL) { 373 | if (PyCallable_Check(h)) { 374 | myowndata.eval_h_python = h; 375 | } else { 376 | PyErr_SetString(PyExc_TypeError, 377 | "Need a callable object for function h."); 378 | retval = NULL; 379 | SAFE_FREE(x_L); 380 | SAFE_FREE(x_U); 381 | SAFE_FREE(g_L); 382 | SAFE_FREE(g_U); 383 | return retval; 384 | } 385 | } else { 386 | logger("[PyIPOPT] Ipopt will use Hessian approximation.\n"); 387 | } 388 | 389 | if (applynew != NULL) { 390 | if (PyCallable_Check(applynew)) { 391 | myowndata.apply_new_python = applynew; 392 | } else { 393 | PyErr_SetString(PyExc_TypeError, 394 | "Need a callable object for function applynew."); 395 | retval = NULL; 396 | SAFE_FREE(x_L); 397 | SAFE_FREE(x_U); 398 | SAFE_FREE(g_L); 399 | SAFE_FREE(g_U); 400 | return retval; 401 | } 402 | } 403 | if (m < 0 || n < 0) { 404 | PyErr_SetString(PyExc_TypeError, "m or n can't be negative"); 405 | retval = NULL; 406 | SAFE_FREE(x_L); 407 | SAFE_FREE(x_U); 408 | SAFE_FREE(g_L); 409 | SAFE_FREE(g_U); 410 | return retval; 411 | } 412 | x_L = (Number *) malloc(sizeof(Number) * n); 413 | x_U = (Number *) malloc(sizeof(Number) * n); 414 | if (!x_L || !x_U) { 415 | retval = PyErr_NoMemory(); 416 | SAFE_FREE(x_L); 417 | SAFE_FREE(x_U); 418 | SAFE_FREE(g_L); 419 | SAFE_FREE(g_U); 420 | return retval; 421 | } 422 | xldata = (double *)xL->data; 423 | xudata = (double *)xU->data; 424 | for (i = 0; i < n; i++) { 425 | x_L[i] = xldata[i]; 426 | x_U[i] = xudata[i]; 427 | } 428 | 429 | g_L = (Number *) malloc(sizeof(Number) * m); 430 | g_U = (Number *) malloc(sizeof(Number) * m); 431 | if (!g_L || !g_U) 432 | PyErr_NoMemory(); 433 | 434 | gldata = (double *)gL->data; 435 | gudata = (double *)gU->data; 436 | 437 | for (i = 0; i < m; i++) { 438 | g_L[i] = gldata[i]; 439 | g_U[i] = gudata[i]; 440 | } 441 | 442 | /* Grab the callback objects because we want to use them later. */ 443 | Py_XINCREF(f); 444 | Py_XINCREF(gradf); 445 | Py_XINCREF(g); 446 | Py_XINCREF(jacg); 447 | Py_XINCREF(h); 448 | Py_XINCREF(applynew); 449 | 450 | /* create the Ipopt Problem */ 451 | 452 | int C_indexstyle = 0; 453 | IpoptProblem thisnlp = CreateIpoptProblem(n, 454 | x_L, x_U, m, g_L, g_U, 455 | nele_jac, nele_hess, 456 | C_indexstyle, 457 | &eval_f, &eval_g, 458 | &eval_grad_f, 459 | &eval_jac_g, &eval_h); 460 | logger("[PyIPOPT] Problem created"); 461 | if (!thisnlp) { 462 | PyErr_SetString(PyExc_MemoryError, "Cannot create IpoptProblem instance"); 463 | retval = NULL; 464 | SAFE_FREE(x_L); 465 | SAFE_FREE(x_U); 466 | SAFE_FREE(g_L); 467 | SAFE_FREE(g_U); 468 | return retval; 469 | } 470 | object = PyObject_NEW(problem, &IpoptProblemType); 471 | 472 | if (object != NULL) { 473 | object->n_variables = n; 474 | object->m_constraints = m; 475 | object->nlp = thisnlp; 476 | dp = (DispatchData *) malloc(sizeof(DispatchData)); 477 | if (!dp) { 478 | retval = PyErr_NoMemory(); 479 | SAFE_FREE(x_L); 480 | SAFE_FREE(x_U); 481 | SAFE_FREE(g_L); 482 | SAFE_FREE(g_U); 483 | return retval; 484 | } 485 | memcpy((void *)dp, (void *)&myowndata, sizeof(DispatchData)); 486 | object->data = dp; 487 | retval = (PyObject *) object; 488 | SAFE_FREE(x_L); 489 | SAFE_FREE(x_U); 490 | SAFE_FREE(g_L); 491 | SAFE_FREE(g_U); 492 | return retval; 493 | } else { 494 | PyErr_SetString(PyExc_MemoryError, "Can't create a new Problem instance"); 495 | retval = NULL; 496 | SAFE_FREE(x_L); 497 | SAFE_FREE(x_U); 498 | SAFE_FREE(g_L); 499 | SAFE_FREE(g_U); 500 | return retval; 501 | } 502 | } 503 | 504 | PyObject *set_intermediate_callback(PyObject * self, PyObject * args) 505 | { 506 | PyObject *intermediate_callback; 507 | problem *temp = (problem *) self; 508 | IpoptProblem nlp = (IpoptProblem) (temp->nlp); 509 | DispatchData myowndata; 510 | DispatchData *bigfield = (DispatchData *) (temp->data); 511 | 512 | /* Init the myowndata field */ 513 | myowndata.eval_intermediate_callback_python = NULL; 514 | 515 | if (!PyArg_ParseTuple(args, "O", &intermediate_callback)) { 516 | return NULL; 517 | } 518 | if (!PyCallable_Check(intermediate_callback)) { 519 | PyErr_SetString(PyExc_TypeError, 520 | "Need a callable object for function!"); 521 | return NULL; 522 | } else { 523 | 524 | bigfield->eval_intermediate_callback_python = 525 | intermediate_callback; 526 | 527 | /* Put a Python function object into this data structure */ 528 | /* 529 | * myowndata.eval_intermediate_callback_python = 530 | * intermediate_callback; 531 | */ 532 | 533 | /* DispatchData *dp = malloc(sizeof(DispatchData)); */ 534 | /* 535 | * memcpy((void*)dp, (void*)&myowndata, 536 | * sizeof(DispatchData)); 537 | */ 538 | /* bigfield = dp; */ 539 | /* 540 | * logger( "qqq: inside set_intermediate_callback, bigfield 541 | * is %p\n", bigfield ) ; 542 | */ 543 | /* 544 | * logger("[PyIPOPT] User specified data field to callback 545 | * function.\n"); 546 | */ 547 | 548 | SetIntermediateCallback(nlp, eval_intermediate_callback); 549 | Py_INCREF(Py_True); 550 | return Py_True; 551 | } 552 | } 553 | 554 | PyObject *solve(PyObject * self, PyObject * args) 555 | { 556 | enum ApplicationReturnStatus status; /* Solve return code */ 557 | int i; 558 | int n; 559 | 560 | /* Return values */ 561 | problem *temp = (problem *) self; 562 | 563 | IpoptProblem nlp = (IpoptProblem) (temp->nlp); 564 | DispatchData *bigfield = (DispatchData *) (temp->data); 565 | int m = temp->m_constraints; 566 | 567 | /* int dX[1]; */ 568 | npy_intp dX[1]; 569 | npy_intp dlambda[1]; 570 | 571 | PyArrayObject *x = NULL, *mL = NULL, *mU = NULL, *lambda = NULL; 572 | Number obj; /* objective value */ 573 | 574 | PyObject *retval = NULL; 575 | PyArrayObject *x0 = NULL; 576 | 577 | PyObject *myuserdata = NULL; 578 | 579 | Number *newx0 = NULL; 580 | 581 | if (!PyArg_ParseTuple(args, "O!|O", &PyArray_Type, &x0, &myuserdata)) { 582 | retval = NULL; 583 | /* clean up and return */ 584 | if (retval == NULL) { 585 | Py_XDECREF(x); 586 | Py_XDECREF(mL); 587 | Py_XDECREF(mU); 588 | Py_XDECREF(lambda); 589 | } 590 | SAFE_FREE(newx0); 591 | return retval; 592 | } 593 | if (x0->nd != 1){ //If x0 is not 1-dimensional then solve will fail and cause a segmentation fault. 594 | logger("[ERROR] x0 must be a 1-dimensional array"); 595 | Py_XDECREF(x); 596 | Py_XDECREF(mL); 597 | Py_XDECREF(mU); 598 | Py_XDECREF(lambda); 599 | PyErr_SetString(PyExc_TypeError, 600 | "x0 passed to solve is not 1-dimensional."); 601 | return NULL; 602 | } 603 | 604 | if (myuserdata != NULL) { 605 | bigfield->userdata = myuserdata; 606 | /* 607 | * logger("[PyIPOPT] User specified data field to callback 608 | * function.\n"); 609 | */ 610 | } 611 | if (nlp == NULL) { 612 | PyErr_SetString(PyExc_TypeError, 613 | "nlp objective passed to solve is NULL\n Problem created?\n"); 614 | retval = NULL; 615 | /* clean up and return */ 616 | if (retval == NULL) { 617 | Py_XDECREF(x); 618 | Py_XDECREF(mL); 619 | Py_XDECREF(mU); 620 | Py_XDECREF(lambda); 621 | } 622 | SAFE_FREE(newx0); 623 | return retval; 624 | } 625 | if (bigfield->eval_h_python == NULL) { 626 | AddIpoptStrOption(nlp, "hessian_approximation", "limited-memory"); 627 | /* logger("Can't find eval_h callback function\n"); */ 628 | } 629 | /* allocate space for the initial point and set the values */ 630 | npy_intp *dim = ((PyArrayObject *) x0)->dimensions; 631 | n = dim[0]; 632 | dX[0] = n; 633 | 634 | x = (PyArrayObject *) PyArray_SimpleNew(1, dX, PyArray_DOUBLE); 635 | if (!x) { 636 | retval = PyErr_NoMemory(); 637 | /* clean up and return */ 638 | if (retval == NULL) { 639 | Py_XDECREF(x); 640 | Py_XDECREF(mL); 641 | Py_XDECREF(mU); 642 | Py_XDECREF(lambda); 643 | } 644 | SAFE_FREE(newx0); 645 | return retval; 646 | } 647 | newx0 = (Number *) malloc(sizeof(Number) * n); 648 | if (!newx0) { 649 | retval = PyErr_NoMemory(); 650 | /* clean up and return */ 651 | if (retval == NULL) { 652 | Py_XDECREF(x); 653 | Py_XDECREF(mL); 654 | Py_XDECREF(mU); 655 | Py_XDECREF(lambda); 656 | } 657 | SAFE_FREE(newx0); 658 | return retval; 659 | } 660 | double *xdata = (double *)x0->data; 661 | for (i = 0; i < n; i++) 662 | newx0[i] = xdata[i]; 663 | 664 | /* Allocate multiplier arrays */ 665 | 666 | mL = (PyArrayObject *) PyArray_SimpleNew(1, dX, PyArray_DOUBLE); 667 | mU = (PyArrayObject *) PyArray_SimpleNew(1, dX, PyArray_DOUBLE); 668 | dlambda[0] = m; 669 | lambda = (PyArrayObject *) PyArray_SimpleNew(1, dlambda, 670 | PyArray_DOUBLE); 671 | 672 | /* For status code, see IpReturnCodes_inc.h in Ipopt */ 673 | 674 | status = 675 | IpoptSolve(nlp, newx0, NULL, &obj, (double *)lambda->data, 676 | (double *)mL->data, (double *)mU->data, 677 | (UserDataPtr) bigfield); 678 | double *return_x_data = (double *)x->data; 679 | for (i = 0; i < n; i++) { 680 | return_x_data[i] = newx0[i]; 681 | } 682 | retval = Py_BuildValue("OOOOdi", 683 | PyArray_Return(x), 684 | PyArray_Return(mL), 685 | PyArray_Return(mU), 686 | PyArray_Return(lambda), 687 | obj, status 688 | ); 689 | /* clean up and return */ 690 | 691 | Py_XDECREF(x); 692 | Py_XDECREF(mL); 693 | Py_XDECREF(mU); 694 | Py_XDECREF(lambda); 695 | 696 | SAFE_FREE(newx0); 697 | return retval; 698 | } 699 | 700 | PyObject *close_model(PyObject * self, PyObject * args) 701 | { 702 | problem *obj = (problem *) self; 703 | DispatchData *dp = obj->data; 704 | 705 | /* Ungrab the callback functions because we do not need them anymore. */ 706 | Py_XDECREF(dp->eval_f_python); 707 | Py_XDECREF(dp->eval_grad_f_python); 708 | Py_XDECREF(dp->eval_g_python); 709 | Py_XDECREF(dp->eval_jac_g_python); 710 | Py_XDECREF(dp->eval_h_python); 711 | Py_XDECREF(dp->apply_new_python); 712 | 713 | FreeIpoptProblem(obj->nlp); 714 | obj->nlp = NULL; 715 | Py_INCREF(Py_True); 716 | return Py_True; 717 | } 718 | 719 | /* static char PYTEST[] = "TestCreate\n"; */ 720 | 721 | /* static PyObject *test(PyObject *self, PyObject *args) */ 722 | /* { */ 723 | /* IpoptProblem thisnlp = NULL; */ 724 | /* problem *object = NULL; */ 725 | /* object = PyObject_NEW(problem , &IpoptProblemType); */ 726 | /* if (object != NULL) */ 727 | /* object->nlp = thisnlp; */ 728 | /* /\* problem *object = problem_new(thisnlp); *\/ */ 729 | /* return (PyObject *)object; */ 730 | /* } */ 731 | 732 | /* Begin Python Module code section */ 733 | static PyMethodDef ipoptMethods[] = { 734 | /* { "solve", solve, METH_VARARGS, PYIPOPT_SOLVE_DOC}, */ 735 | {"create", create, METH_VARARGS, PYIPOPT_CREATE_DOC}, 736 | /* { "close", close_model, METH_VARARGS, PYIPOPT_CLOSE_DOC}, */ 737 | /* { "test", test, METH_VARARGS, PYTEST}, */ 738 | {"set_loglevel", set_loglevel, METH_VARARGS, PYIPOPT_LOG_DOC}, 739 | {NULL, NULL} 740 | }; 741 | 742 | #if PY_MAJOR_VERSION >= 3 743 | #define MOD_ERROR_VAL NULL 744 | #define MOD_SUCCESS_VAL(val) val 745 | #define MOD_INIT(name) PyMODINIT_FUNC PyInit_##name(void) 746 | #define MOD_DEF(ob, name, doc, methods) \ 747 | static struct PyModuleDef moduledef = { \ 748 | PyModuleDef_HEAD_INIT, name, doc, -1, methods, }; \ 749 | ob = PyModule_Create(&moduledef); 750 | #else 751 | #define MOD_ERROR_VAL 752 | #define MOD_SUCCESS_VAL(val) 753 | #define MOD_INIT(name) void init##name(void) 754 | #define MOD_DEF(ob, name, doc, methods) \ 755 | ob = Py_InitModule3(name, methods, doc); 756 | #endif 757 | 758 | MOD_INIT(pyipoptcore) 759 | { 760 | PyObject * m; 761 | /* Finish initialization of the problem type */ 762 | if (PyType_Ready(&IpoptProblemType) < 0) { 763 | return MOD_ERROR_VAL; 764 | } 765 | 766 | MOD_DEF(m, "pyipoptcore", "A hook between Ipopt and Python", ipoptMethods) 767 | 768 | if (m == NULL) 769 | return MOD_ERROR_VAL; 770 | 771 | /* Initialize numpy. */ 772 | /* A segfault will occur if I use numarray without this.. */ 773 | import_array(); 774 | if (PyErr_Occurred()) { 775 | Py_FatalError("Unable to initialize module pyipoptcore"); 776 | } 777 | 778 | return MOD_SUCCESS_VAL(m); 779 | } 780 | 781 | /* End Python Module code section */ 782 | --------------------------------------------------------------------------------