Using PyCapsules
Instead of passing pure Python callables to ipyopt.Problem
which causes some overhead when calling the Python callables from C++,
we also can pass C callbacks encapsulated in PyCapsule objects.
Warning
Segfaults
When working with C callbacks inside PyCapsule objects, ipyopt will always assume, that the C callbacks have the correct signature. If this is not the case, expect memory errors and thus crashes.
PyCapsule can be defined in C extensions. For an example on how to do this from scratch, see module.c.
A much more convenient way is to use Cython. Then you don’t need to write boilerplate code to create a python module. All you still need to do is to define the objective function, the constraint residuals and their derivatives:
import cython
cdef extern from "stdbool.h":
ctypedef bint bool
cdef api:
bool f(int n, const double *x, double *obj_value, void* userdata):
obj_value[0] = x[0] * x[3] * (x[0] + x[1] + x[2]) + x[2]
return True
bool grad_f(int n, const double *x, double *out, void *userdata):
out[0] = x[0] * x[3] + x[3] * (x[0] + x[1] + x[2])
out[1] = x[0] * x[3]
out[2] = x[0] * x[3] + 1.0
out[3] = x[0] * (x[0] + x[1] + x[2])
return True
bool g(int n, const double *x, int m, double *out, void *userdata):
out[0] = x[0] * x[1] * x[2] * x[3]
out[1] = x[0] * x[0] + x[1] * x[1] + x[2] * x[2] + x[3] * x[3]
return True
bool jac_g(int n, const double *x, int m, int n_out, double *out,
void *userdata):
out[0] = x[1] * x[2] * x[3]
out[1] = x[0] * x[2] * x[3]
out[2] = x[0] * x[1] * x[3]
out[3] = x[0] * x[1] * x[2]
out[4] = 2.0 * x[0]
out[5] = 2.0 * x[1]
out[6] = 2.0 * x[2]
out[7] = 2.0 * x[3]
out[8] = x[0] * x[1] * x[2] * x[3]
return True
bool h(int n, const double *x, double obj_factor, int m,
const double *lagrange, int n_out, double *out, void *userdata):
out[0] = obj_factor * (2 * x[3])
out[1] = obj_factor * (x[3])
out[2] = 0
out[3] = obj_factor * (x[3])
out[4] = 0
out[5] = 0
out[6] = obj_factor * (2 * x[0] + x[1] + x[2])
out[7] = obj_factor * (x[0])
out[8] = obj_factor * (x[0])
out[9] = 0
out[1] += lagrange[0] * (x[2] * x[3])
out[3] += lagrange[0] * (x[1] * x[3])
out[4] += lagrange[0] * (x[0] * x[3])
out[6] += lagrange[0] * (x[1] * x[2])
out[7] += lagrange[0] * (x[0] * x[2])
out[8] += lagrange[0] * (x[0] * x[1])
out[0] += lagrange[1] * 2
out[2] += lagrange[1] * 2
out[5] += lagrange[1] * 2
out[9] += lagrange[1] * 2
return True
To just in time compile the pyx
, file, you can use Cython’s
pyximport
:
import pyximport
pyximport.install(language_level=3)
The compiled C extension can then be included. It will contain an
attribute __pyx_capi__
containing the PyCapsules:
from hs071_capsules import __pyx_capi__ as capsules
nlp = ipyopt.Problem(
...
capsules["f"],
capsules["grad_f"],
capsules["g"],
capsules["jac_g"],
capsules["h"],
)