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"],
)