私は、C 最適化ライブラリーである donlp2 用の単純な cython ラッパーを作成しました。ライブラリはグローバル変数を広範囲に使用し、関数がそれらを呼び出すことができるように、呼び出し元が定義済みの名前で関数を記述していることを前提としています。(例えば、それぞれ関数とその勾配を評価する関数 ef と egradf があります)
ラッパーは、グローバル変数に「cdef extern」を使用し、C ライブラリが期待する関数を作成するために「cdef public」を使用して、非常に簡単に作成できました。また、view.array を使用して、python 関数に渡すことができる cython 配列への double* ポインターをキャストしました。私のラッパーがCライブラリを使用して、純粋なPythonで定義された関数と勾配を最適化できるようにします。
以下はラッパーコードです。
from libc.string cimport strcpy
from cython cimport view
cdef extern void donlp2()
#global varaibles used by donlp2
#only import those variables that are necessary
cdef extern int n
cdef extern int nlin
cdef extern int nonlin
cdef extern int nstep
cdef extern int iterma
cdef extern int icf
cdef extern int icgf
cdef extern double *x
cdef extern char name[41]
cdef extern double del0
cdef extern double tau
cdef extern double tau0
cdef extern int analyt
cdef extern double epsdif
cdef extern int nreset
cdef extern int silent
cdef extern double *low
cdef extern double *up
cdef extern double optite
#Below are used only if bloc is True
cdef extern double *xtr
cdef extern double *fu
cdef extern double **fugrad
cdef extern int bloc
class DonlpProblem:
"""
Contains all the inputs, including python functions, to
solve a constrained nonlinear problem using donlp2.
"""
def __init__(self,
x0,
ef,
egradf,
low,
up,
nonlin,
activeConstraintTolerance,
name,
bloc=False,
eval_extern=None,
econ=None,
maxIter=4000,
maxBacktrackIter=20,
descentVsFeasibilityWeight=0.1,
analyticDerivatives=True,
silent=False,
nreset=4):
self.bloc = bloc
if self.bloc:
self.eval_extern = eval_extern
else:
self.ef = ef
self.egradf = egradf
self.econ = econ
self.n = x0.size
assert(nonlin+self.n == low.size)
assert(nonlin+self.n == up.size)
self.x0 = x0
self.low = low
self.up = up
self.nonlin = nonlin
self.maxIter = maxIter
self.maxBacktrackIter = maxBacktrackIter
self.name = name
self.activeConstraintTolerance = activeConstraintTolerance
self.descentVsFeasibilityWeight = descentVsFeasibilityWeight
self.silent = silent
self.analyticDerivatives = analyticDerivatives
self.nreset = nreset
def run(self):
"""
Solve problem using donlp2.
"""
global globalDonlpProblem
globalDonlpProblem = self
donlp2()
def _user_init_size(self):
"""
Set global variables related to problem size and maximum number
of iterations.
"""
global n, nlin, nonlin, iterma, nstep
n = self.n
nlin = 0
nonlin = self.nonlin
iterma = self.maxIter
nstep = self.maxBacktrackIter
def _user_init(self):
"""
Initialize various problem data unrelated to sizes. This includes
the problem name, initial point, tolerances, bound constraints,
and whether analytic gradients are given.
"""
global name, x, del0, tau0, tau, analyt, epsdif, nreset
global silent, low, up, bloc
strcpy(name, self.name)
for i, xi in enumerate(self.x0):
x[i+1] = xi
for i, lowi in enumerate(self.low):
low[i+1] = lowi
for i, upi in enumerate(self.up):
up[i+1] = upi
bloc = <int> self.bloc
del0 = self.activeConstraintTolerance
tau0 = 0.5e0
tau = self.descentVsFeasibilityWeight
analyt = <int>self.analyticDerivatives
epsdif = 0.e0
nreset = self.nreset
silent = <int>self.silent
cdef public void user_init_size():
"""
Called by donlp, delegate to problem object.
"""
globalDonlpProblem._user_init_size()
cdef public void user_init(void):
"""
Called by donlp, delegate to problem object.
"""
globalDonlpProblem._user_init()
cdef public void ef(double *x, double *fx):
"""
Called by donlp, delegate to problem object.
"""
global icf
icf += 1
cdef int xSize = globalDonlpProblem.n+1
cdef view.array xarr = <double[:xSize]> x
fx[0] = globalDonlpProblem.ef(xarr[1:])
cdef public void egradf(double *x, double *gradf):
"""
Called by donlp, delegate to problem object.
"""
global icgf
icgf += 1
cdef int xSize = globalDonlpProblem.n+1
cdef view.array xarr = <double[:xSize]> x
cdef view.array gradArr = <double [:xSize]> gradf
globalDonlpProblem.egradf(xarr[1:], gradArr[1:])
cdef public void eval_extern(int mode):
"""
Called by donlp, delegate to problem object.
"""
global icf, icgf
global fu, fugrad
cdef int xSize = globalDonlpProblem.n+1
cdef view.array xarr = <double[:xSize]> xtr
if mode == 1:
icf += 1
fu[0] = globalDonlpProblem.eval_extern(mode, xarr[1:])
elif mode == 2:
icf += 1
icgf += 1
tmp1, tmp2 = globalDonlpProblem.eval_extern(mode, xarr[1:])
fu[0] = tmp1
for i in range(tmp2.size):
fugrad[i+1][0] = tmp2[i]
cdef public void econ(int type, int *liste, double *x,
double *con, int *err):
pass
cdef public void econgrad(int *liste, int shift,
double *x, double **grad):
pass
cdef public void newx(double *x, double *u, int itstep,
double **accinf, int *cont):
cont[0] = 1
cdef public void setup(void):
pass
cdef public void solchk(void):
pass
ラッパー コードは、次のようないくつかの単純なおもちゃのケースで機能します。
import cydon
import numpy as np
def main():
def ef(x):
return 100*(x[1]-x[0]**2)**2 + (x[0]-1)**2
def egradf(x, g):
g[0] = 200*(x[0]**2-x[1])*x[0] + 2*(x[0]-1)
g[1] = 200*(x[1] - x[0]**2)
x0 = np.array([15,-15])
n = x0.size
low = -1.0e10 * np.ones(n)
up = 1.0e10 * np.ones(n)
def eval_extern(mode, x):
fx = 100*(x[1]-x[0]**2)**2 + (x[0]-1)**2
if mode == 1:
return fx
elif mode == 2:
gradfx = np.ones(2)
gradfx[0] = 200*(x[0]**2-x[1])*x[0] + 2*(x[0]-1)
gradfx[1] = 200*(x[1] - x[0]**2)
return fx, gradfx
problem = cydon.DonlpProblem(
x0=x0,
ef=None,
egradf=None,
bloc=True,
eval_extern=eval_extern,
activeConstraintTolerance=1.00e-1,
low=low,
up=up,
nonlin=0,
silent=False,
name="blabloc"
)
problem.run()
if __name__ == "__main__":
main()
私が実際に解決したい問題には、numpy と cvxopt を使用した配列操作を使用した、より多くのセットアップが含まれます。私がそれを作成すると、コードはすぐにセグメンテーション違反になります。gdb をステップ実行して valgrind を使用すると、次のような最適化ライブラリの行のみが明らかになります。
foo = malloc_wrapper(size);
valgrind からの次のエラーで終了します。
==31631== Process terminating with default action of signal 11 (SIGSEGV)
==31631== Bad permissions for mapped region at address 0x8BFF930
==31631== at 0x17984DBC: global_mem_malloc (donlp2.c:8690)
==31631== by 0x17985FA1: donlp2 (donlp2.c:204)
==31631== by 0x179504D2: __pyx_pw_5cydon_12DonlpProblem_3run (cydon.c:2215)
==31631== by 0x4E78BD7: PyObject_Call (abstract.c:2529)
==31631== by 0x4F1BFA1: PyEval_EvalFrameEx (ceval.c:4239)
==31631== by 0x4F22C08: PyEval_EvalCodeEx (ceval.c:3253)
==31631== by 0x4F209B4: PyEval_EvalFrameEx (ceval.c:4117)
==31631== by 0x4F21E47: PyEval_EvalFrameEx (ceval.c:4107)
==31631== by 0x4F22C08: PyEval_EvalCodeEx (ceval.c:3253)
==31631== by 0x4F22C81: PyEval_EvalCode (ceval.c:667)
==31631== by 0x4F46350: PyRun_FileExFlags (pythonrun.c:1370)
==31631== by 0x4F465F6: PyRun_SimpleFileExFlags (pythonrun.c:948)
セグメンテーション違反は、C ライブラリが実際の作業を行う前に発生します。変数を初期化するだけです。ライン 8690 は
foo = malloc_wrapper(sizeOfMalloc);
204行目は単なる呼び出しです
global_mem_malloc();
インクルードされたヘッダー ファイルでは、foo は double* であると定義されています。malloc_wrapper 内のメモリ割り当てが成功し、関数が正常に返されたことに注意してください。失敗しているのは foo への書き込みです。
これを引き起こしている原因を絞り込む方法、またはそれを修正する方法について何か提案はありますか?