跳到主要内容
版本:v0.7.0 beta

Python部署

这里介绍了如何在Python中调用生成的代码.

这里以Ubuntu系统为例, 介绍如何在Python中调用生成的代码. 其他平台的使用方法类似, 只需要安装对应的平台依赖.

我们以最速降线问题为例, 该示例的文件结构如下 (只展示与Python部署相关的文件):

working_dir/
├── brachistochrone/
│ ├── brachistochrone_prob.c
│ ├── brachistochrone_prob.h
│ ├── brachistochrone_solver.h
│ ├── libbrachistochrone_solver_static.a
├── brachistochrone.py
├── pyapi_build.sh
├── pyapi_test.py
.

其中:

  • brachistochrone.py是模型文件, 该文件中定义了最速降线问题的模型, 以及代码生成的相关设置 (其中platform选择'linux-x86_64-gcc' (Linux平台) , lib_type选择'static' (静态链接库))
  • brachistochrone/ 是代码生成的目录
  • pyapi_build.sh 是构建Python接口的脚本
  • pyapi_test.py 是测试Python接口的脚本

构建Python接口

这里我们采用ctypeslib2工具来构建Python接口. 该工具依赖clang工具链. 在Ubuntu系统中, 可以通过以下命令安装:

sudo apt install clang-14 libclang-14-dev
pip3 install ctypeslib2 clang==14

自动构建Python接口的步骤如下:

  1. 将所有的源码 (例如brachistochrone_prob.c, libbrachistochrone_solver_static.a以及用户自定义的外部函数) 编译成一个共享库 (例如libbrachistochrone.so), 以便Python接口调用
  2. 使用clang2py工具生成Python接口文件 (例如pyapi_brachistochrone.py), 该文件中包含了调用共享库的Python函数定义

下面是一个示例的构建脚本pyapi_build.sh, 该脚本完成了上述的构建步骤:

pyapi_build.sh

codegen_dir=brachistochrone/
name=brachistochrone
# 1. Compile the prob.c and solver.a into a single shared library
## -Wl,--whole-archive: force the linker to include all object files from the static library
## -Wl,--no-whole-archive: reset the linker behavior to default for subsequent libraries
gcc -shared -fPIC -o $codegen_dir/lib$name.so \
-Wl,--whole-archive $codegen_dir/lib${name}_solver_static.a \
$codegen_dir/${name}_prob.c \
-Wl,--no-whole-archive

# 2. Generate the Python API wrapper using clang2py
clang2py $codegen_dir/${name}_prob.h $codegen_dir/${name}_solver.h \
-o pyapi_${name}.py \
-l $codegen_dir/lib${name}.so \
-k cdefstum \
-x

使用Python接口

下面为一个示例的测试脚本pyapi_test.py, 该脚本调用了生成的Python接口来求解最速降线问题, 可以看到Python接口的使用与C接口非常类似, 主要区别在于需要使用ctypes.byref来传递参数的引用.

pyapi_test.py
from pyapi_brachistochrone import *
import ctypes

prob = Brachistochrone_Problem()
option = Brachistochrone_Option()
ws = Brachistochrone_WorkSpace()
output = Brachistochrone_Output()

brachistochrone_init(
ctypes.byref(prob),
ctypes.byref(option),
ctypes.byref(ws)
)

prob.param[BRACHISTOCHRONE_PARAM_XF] = 2.0
prob.param[BRACHISTOCHRONE_PARAM_YF] = 2.0

# option
option.max_num_iter = 100
option.print_level = 2

for index in range(BRACHISTOCHRONE_DIM_N):
# initial guess
ratio = float(BRACHISTOCHRONE_DIM_N - 1 - index) / float(BRACHISTOCHRONE_DIM_N - 1)
ws.primal.var[index][BRACHISTOCHRONE_VAR_X] = (1.0 - ratio) * prob.param[BRACHISTOCHRONE_PARAM_XF]
ws.primal.var[index][BRACHISTOCHRONE_VAR_Y] = (1.0 - ratio) * prob.param[BRACHISTOCHRONE_PARAM_YF]
ws.primal.var[index][BRACHISTOCHRONE_VAR_V] = 0.0
ws.primal.var[index][BRACHISTOCHRONE_VAR_U] = 0.0
ws.primal.var[index][BRACHISTOCHRONE_VAR_TF] = 1.0

solve_status = brachistochrone_solve(
ctypes.byref(prob),
ctypes.byref(option),
ctypes.byref(ws),
ctypes.byref(output)
)

print(f"minimum time: {output.obj}")