1、C++调用Python

1.1、 背景

最近实验室有一个项目要用遗传算法和粒子群算法解决问题,但是没有找到在windows上合适的C++库,但是Python的库很多(例如说pymoo: Multi-objective Optimization in Python),所以决定采用在C++中嵌入python解释器的方法,使用C++调用Python的接口。

1.2、环境安装

安装Python到指定文件夹

在Windows内离线部署python:
https://www.python.org/ftp/python
这里选择的是3.8.9版本:
https://www.python.org/ftp/python/3.8.9/python-3.8.9-amd64.exe
然后选择安装到指定文件夹的Python38目录下。

安装Python第三方库

pip安装环境依赖
根据pymoo的指引进行安装,因为github访问不稳定,可以使用proxychains4代理。

git clone https://github.com/anyoptimization/pymoo

现在目录格式像这样:

.
├── Python38
│   ├── DLLs
│   ├── Doc
│   ├── LICENSE.txt
│   ├── Lib
│   ├── NEWS.txt
│   ├── Scripts
│   ├── Tools
│   ├── include
│   ├── libs
│   ├── python.exe
│   ├── python3.dll
│   ├── python38.dll
│   ├── pythonw.exe
│   ├── share
│   ├── tcl
│   ├── vcruntime140.dll
│   └── vcruntime140_1.dll
└── pymoo
    ├── LICENSE
    ├── MANIFEST.in
    ├── Makefile
    ├── README.rst
    ├── data
    ├── pymoo
    ├── setup.py
    └── tests

执行以下命令安装环境

cd pymoo
 ..\Python38\python.exe -m pip install .

调试环境

如果需要在Debug方式下运行,需要将Python38目录下libs目录下的python38.lib copy一份,重命名为python38_d.lib,即可以使用。

编译设置

将Python38/include目录设置为包含目录。
将Python38/libs设置为库目录。
将Python38.dll拷贝一份到执行文件的目录下。

1.3、编程开发

Python的相关接口

'''
Problem defination
Problem link: https://www.pymoo.org/getting_started/part_2.html
\begin{align}
\label{eq:getting_started_pymoo}
\begin{split}
\min \;\; & f_1(x) = 100 \, (x_1^2 + x_2^2) \\
\min \;\; & f_2(x) = (x_1-1)^2 + x_2^2 \\[1mm]
\text{s.t.} \;\; & g_1(x) = 2 \, (x_1 - 0.1) \, (x_1 - 0.9)  \, /  \,  0.18 \leq 0\\
& g_2(x) = - 20 \, (x_1 - 0.4) \, (x_1 - 0.6) \,  /  \,  4.8 \leq 0\\[1mm]
& -2 \leq x_1 \leq 2 \\
& -2 \leq x_2 \leq 2\\[1mm]
& x \in \mathbb{R}
\end{split}
\end{align}
'''
import numpy as np
from pymoo.core.problem import ElementwiseProblem

class MyProblem(ElementwiseProblem):

    def __init__(self):
        super().__init__(n_var=2,
                         n_obj=2,
                         n_constr=2,
                         xl=np.array([-2,-2]),
                         xu=np.array([2,2]))

    def _evaluate(self, x, out, *args, **kwargs):
        f1 = 100 * (x[0]**2 + x[1]**2)
        f2 = (x[0]-1)**2 + x[1]**2

        g1 = 2*(x[0]-0.1) * (x[0]-0.9) / 0.18
        g2 = - 20*(x[0]-0.4) * (x[0]-0.6) / 4.8

        out["F"] = [f1, f2]
        out["G"] = [g1, g2]


# Define a Algorithm
from pymoo.algorithms.moo.nsga2 import NSGA2
from pymoo.factory import get_sampling, get_crossover, get_mutation

 
# Define a Termination Criterion
from pymoo.factory import get_termination


#Optimize
from pymoo.optimize import minimize


def run():
    problem = MyProblem()
    
    algorithm = NSGA2(
        pop_size=40,
        n_offsprings=10,
        sampling=get_sampling("real_random"),
        crossover=get_crossover("real_sbx", prob=0.9, eta=15),
        mutation=get_mutation("real_pm", eta=20),
        eliminate_duplicates=True
    )
    
    termination = get_termination("n_gen", 40)
    
    res = minimize(problem,
               algorithm,
               termination,
               seed=1,
               save_history=True,
               verbose=True)
    X = res.X.tolist()
    F = res.F.tolist()
    # print(res.X)
    # print(res.F)
    return X, F

C++调用Python和相关的类型转换

#include <Python.h>
#include <iostream>
#include <vector>
using namespace std;


vector<vector<double>> list2DToVector2D(PyObject* pReturn) {
	vector<vector<double>> ret;
	if (PyList_Check(pReturn)) { 
		int SizeOfList = PyList_Size(pReturn);
		for (int i = 0; i < SizeOfList; i++) {
			PyObject* ListItem = PyList_GetItem(pReturn, i);

			int NumOfItems = PyList_Size(ListItem);
			vector<double> temp;
			for (int j = 0; j < NumOfItems; j++) {
				PyObject* Item = PyList_GetItem(ListItem, j);
				double result;
				PyArg_Parse(Item, "d", &result);
				temp.push_back(result);
			}
			ret.push_back(temp);
		}
	}
	else {
		cout << "parse_two_dimension_list error" << endl; 
	}
	return ret;
}

void showVector2D(vector<vector<double>> v) {
	for (int i = 0; i < v.size(); i++) {
		auto& vv = v[i];
		for (int j = 0; j < vv.size(); j++) {
			cout << vv[j] << " ";
		}
		cout << "\n";
	}
}
void do_multi_objective_optimization()
{
	vector<vector<double>> X;
	vector<vector<double>> Y;

	PyObject* pModule = NULL;
	PyObject* pFunc = NULL;
	PyObject* pDict = NULL;
	PyObject* pReturn = NULL;

	if (0 == Py_IsInitialized())
	{
		cout << "Initialize error\n";
		return;
	}

	pModule = PyImport_ImportModule("multi-objective-optimization");
	if (!pModule) {
		cout << "load error\n";
		return;
	}
	pDict = PyModule_GetDict(pModule);
	pFunc = PyDict_GetItemString(pDict, "run");
	
	
	pReturn = PyEval_CallObject(pFunc, NULL);
	
	if (PyTuple_Check(pReturn)) {
		int sizeOfTuple = PyTuple_Size(pReturn);
		if (sizeOfTuple == 2) {
			PyObject* Item;
			Item = PyTuple_GetItem(pReturn, 0);
			X = list2DToVector2D(Item);
			Item = PyTuple_GetItem(pReturn, 1);
			Y = list2DToVector2D(Item);
		}
	}
	else {
		cout << "Not a Tuple" << endl;
	}

	Py_DECREF(pReturn);
	Py_DECREF(pModule);
    
	showVector2D(X);
	showVector2D(Y);
}


int main()
{
	Py_SetPythonHome(L"D:\\Code\\cpp_py_interpreter\\Python38");
	Py_Initialize();
	do_multi_objective_optimization();
	Py_Finalize();
}