Skip to main content
Version: v0.7.0 beta

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.py is the model file, which defines the model of the Brachistochrone problem and the related settings for code generation (where platform is set to 'linux-x86_64-gcc' (Linux platform) and lib_type is set to 'static' (static library))
  • brachistochrone/ is the code generation directory
  • pyapi_build.sh is the script for building the Python interface
  • pyapi_test.py is 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:

  1. 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
  2. Use the clang2py tool 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:

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

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.

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}")