Python Deployment
This section introduces how to call the generated code in Python. Here, we take the Brachistochrone problem as an example. The file structure of this example is as follows (only showing files related to Python deployment):
working_dir/
├── brachistochrone/
│ ├── brachistochrone_prob.c
│ ├── brachistochrone_prob.h
│ ├── brachistochrone_solver.h
│ ├── libbrachistochrone_solver_static.a
├── brachistochrone.py
├── pyapi_build.sh
├── pyapi_test.py
.
Where:
brachistochrone.pyis the model file, which defines the model of the Brachistochrone problem and the related settings for code generation (whereplatformis set to'linux-x86_64-gcc'(Linux platform) andlib_typeis set to'static'(static library))brachistochrone/is the code generation directorypyapi_build.shis the script for building the Python interfacepyapi_test.pyis the script for testing the Python interface
Building the Python Interface
Here we use the ctypeslib2 tool to build the Python interface. This tool depends on the clang toolchain. On Ubuntu systems, you can install it with the following command:
sudo apt install clang-14 libclang-14-dev
pip3 install ctypeslib2 clang==14
The steps to automatically build the Python interface are as follows:
- Compile all source code (e.g.,
brachistochrone_prob.c,libbrachistochrone_solver_static.a, and user-defined external functions) into a single shared library (e.g.,libbrachistochrone.so) for Python interface calls - Use the
clang2pytool to generate the Python interface file (e.g.,pyapi_brachistochrone.py), which contains the Python function definitions for calling the shared library
Here is an example build script pyapi_build.sh that completes the above build steps:
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
Using the Python Interface
Here is an example test script pyapi_test.py that calls the generated Python interface to solve the Brachistochrone problem. You can see that the usage of the Python interface is very similar to the C interface, with the main difference being that you need to use ctypes.byref to pass references to the parameters.
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}")