オッサンはDesktopが好き

自作PCや機械学習、自転車のことを脈絡無く書きます

Ubuntu 20.04LTSにboost numpyをインストールする

自分用のメモ
以前に書いたやり方*1で入らなかったので,ここ*2を参考に手順を再構築

1. インストール

ここ*3からソースをダウンロード
boost ver.は以前に使っていた1.73.0
pythonはver.3.8を指定

$ sudo cp boost_1_73_0.tar.gz -r /opt/
$ cd /opt
$ sudo tar zxvf boost_1_73_0.tar.gz
$ sudo rm boost_1_73_0.tar.gz
$ cd boost_1_73_0
$ sudo ./bootstrap.sh --with-libraries=python --with-python=python3 --with-python-version=3.8
$ sudo ./b2 --prefix=/opt/boost_1_73_0 install
$ find /opt -name *boost_python*

libboost_python38が入っている事を確認

$ find /opt -name *boost_numpy*

libboost_numpy38が入っている事を確認

Note:
・よく分からなかったけど,以前には必要なかったオプション"--with-libraries=python --with-python=python3"が要るっぽかった.これが無いとlibboost_numpy38がビルドされない
・インストール先を"--prefix=/opt/boost_1_73_0 install"としているので/usr/libには入らない.多分,"-j8"オプションで汎用ディレクトリに入る

2. C言語ライブラリのビルド & Pythonからのコール

boost_sample.cpp

#include "boost/python/numpy.hpp"
#include <stdexcept>
#include <algorithm>

#include <iostream>
#include <fstream>
#include <random>
#include <sys/stat.h>

namespace p = boost::python;
namespace np = boost::python::numpy;

using namespace std;

np::ndarray multiply_matrix(np::ndarray a, np::ndarray b, int size)
{
    int nd = a.get_nd();
    if (nd != 1)
    {
        throw std::runtime_error("a must be 1-dimensional");
    }
    if (a.get_dtype() != np::dtype::get_builtin<double>())
    {
        throw std::runtime_error("a must be float64 array");
    }
    double *A = reinterpret_cast<double *>(a.get_data());

    nd = b.get_nd();
    if (nd != 1)
    {
        throw std::runtime_error("b must be 1-dimensional");
    }
    if (b.get_dtype() != np::dtype::get_builtin<double>())
        throw std::runtime_error("b must be float64 array");
    double *B = reinterpret_cast<double *>(b.get_data());
    
    vector<double> ma;
    for(int i = 0; i < size; i++)
    {
        for(int j = 0; j < size; j++)
        {
            double tmp = 0.0;
            for(int k = 0; k < size; k++)
            {
                tmp += A[i*size + k]*B[k*size + j];
            }
            ma.push_back(tmp);
        }
    }

    Py_intptr_t shape[2] = {size, size};
    np::ndarray result = np::zeros(2, shape, np::dtype::get_builtin<double>());
    std::copy(ma.begin(), ma.end(), reinterpret_cast<double*>(result.get_data()));
    return result;
}

double rand_uniform(double min, double max)
{
    double width = max - min;
    std::random_device rnd;
    std::mt19937 mt(rnd());
    std::uniform_int_distribution<> rand10000(0, 10000*width);
    int randi = rand10000(mt);
    double val = min + (float)randi/10000.0;
    return val;
}

int main(int argc, char* argv[])
{
    cout << "Hellow" << endl;
    Py_Initialize();
    np::initialize();
    
    double a[5*5];
    double b[5*5];
    int m_size = 5*5;
    for(int i = 0; i < 5; i++)
    {
        for(int j = 0; j < 5; j++)
        {
            a[i*5 + j] = rand_uniform(-1.0, 1.0); //random.uniform(-10.0, 10.0)
            b[i*5 + j] = rand_uniform(-1.0, 1.0); //random.uniform(-10.0, 10.0)
        }
    }

    cout << "input a:" << endl;
    cout << a[0] << "," << a[1] << "," << a[2] << "," << a[3] << "," << a[4] << endl;
    cout << a[5] << "," << a[6] << "," << a[7] << "," << a[8] << "," << a[9] << endl;
    cout << a[10] << "," << a[11] << "," << a[12] << "," << a[13] << "," << a[14] << endl;
    cout << a[15] << "," << a[16] << "," << a[17] << "," << a[18] << "," << a[19] << endl;
    cout << a[20] << "," << a[21] << "," << a[22] << "," << a[23] << "," << a[24] << endl;

    cout << "input b:" << endl;
    cout << b[0] << "," << b[1] << "," << b[2] << "," << b[3] << "," << b[4] << endl;
    cout << b[5] << "," << b[6] << "," << b[7] << "," << b[8] << "," << b[9] << endl;
    cout << b[10] << "," << b[11] << "," << b[12] << "," << b[13] << "," << b[14] << endl;
    cout << b[15] << "," << b[16] << "," << b[17] << "," << b[18] << "," << b[19] << endl;
    cout << b[20] << "," << b[21] << "," << b[22] << "," << b[23] << "," << b[24] << endl;

    p::tuple shape = p::make_tuple(m_size);
    p::tuple stride = p::make_tuple(sizeof(double));
    np::dtype dt = np::dtype::get_builtin<double>();
    np::ndarray a_np = np::from_data(&a[0], dt, shape, stride, p::object());
    np::ndarray b_np = np::from_data(&b[0], dt, shape, stride, p::object());

    np::ndarray result_np = multiply_matrix(a_np, b_np, 5);

    double *result = reinterpret_cast<double *>(result_np.get_data());
    cout << "result:" << endl;
    cout << result[0] << "," << result[1] << "," << result[2] << "," << result[3] << "," << result[4] << endl;
    cout << result[5] << "," << result[6] << "," << result[7] << "," << result[8] << "," << result[9] << endl;
    cout << result[10] << "," << result[11] << "," << result[12] << "," << result[13] << "," << result[14] << endl;
    cout << result[15] << "," << result[16] << "," << result[17] << "," << result[18] << "," << result[19] << endl;
    cout << result[20] << "," << result[21] << "," << result[22] << "," << result[23] << "," << result[24] << endl;

    return 0;
}

BOOST_PYTHON_MODULE(libsample) {
  Py_Initialize();
  np::initialize();
  p::def("multiply_matrix", multiply_matrix);
}

・mainとrand_uniformはデバッグ
・”using namespace std”は普段使わないけど面倒かったので...

CMakeLists.txt

project(sample)
cmake_minimum_required(VERSION 3.0)

set(BOOST_ROOT /opt/boost_1_73_0)

### C++11
add_compile_options(-std=c++11)

### pkgconfig (for pkg_check_modules)
find_package(PkgConfig REQUIRED)

### Python includes
pkg_check_modules(PYTHON3 python3 REQUIRED)
include_directories(${PYTHON3_INCLUDE_DIRS})

### Boost includes
include_directories(${BOOST_ROOT}/include)
link_directories(${BOOST_ROOT}/lib)

### Build
add_library(sample SHARED boost_sample.cpp)
set_target_properties(sample PROPERTIES SUFFIX ".so")

target_link_libraries(sample boost_numpy38 boost_python38)

boost_sample_call.py

import numpy as np
import libsample as s
import random

if __name__ == "__main__":
    a = np.zeros([5, 5])
    b = np.zeros([5, 5])
    for i in range(5):
        for j in range(5):
            a[i, j] = random.uniform(-10.0, 10.0)
            b[i, j] = random.uniform(-10.0, 10.0)
    print(np.matmul(a, b))

    result = s.multiply_matrix(a.reshape([5*5]), b.reshape(5*5), 5)
    print(result)

ビルドして実行

$ mkdir build
$ cd build
$ cmake ..
$ make
$ cd ..
$ cp build/*.so ./
$ python3 boost_sample_call.py

多次元配列をC言語側に渡して,答えを受け取る

3. C言語ソースのデバッグ

~/.bashrc

export LD_LIBRARY_PATH="/opt/boost_1_73_0/lib:$PATH"

task.json

{
    "tasks": [
        {
            "type": "shell",
            "label": "C/C++: g++-9 build active file",
            "command": "/usr/bin/g++-9",
            "args": [
                "-fdiagnostics-color=always",
                "-g",
                "${file}",
                "-o",
                "${fileDirname}/${fileBasenameNoExtension}",
                "-std=c++11",
                "-I/opt/boost_1_73_0/include",
                "-I/usr/include/python3.8",
                "-I/usr/include/x86_64-linux-gnu/python3.8",
                "-L/usr/bin",
                "-lpython3.8",
                "-L/opt/boost_1_73_0/lib",
                "-lboost_numpy38",
                "-lboost_python38"
            ],
            "options": {
                "cwd": "${fileDirname}"
            },
            "problemMatcher": [
                "$gcc"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "detail": "Task generated by Debugger."
        }
    ],
    "version": "2.0.0"
}

・typeをcppbuild→shellにすること
・"-L/opt/boost_1_73_0/lib"で実際に入ったパスを指定すれば,/usr/libにライブラリが入らなくても問題ない

ブレーク出来た.配列(malloc, vector含めて)の中身は直接見えないが,デバッガーが無いよりはだいぶマシ

以上