概要
PythonからC++のクラスや関数にアクセスする方法は幾つかあると思いますが、PyROOTはすごく簡単な方ではないかと思います。なお、C++とC言語は呼び出し情報/シグネチャが異なるためPyROOTではC言語は直接サポートされていませんが、extern "C"
を使えばOKのようです。
英語でも問題ない方は下記のサイトを参照したほうが良いかもしれません。 CERN ROOTのホームページのPythonページです。
実行環境
実行環境はMacOSです。
LinuxとWindowsではPyROOTを使ったことはありますが、自分が作成したC++のプログラムとの連携を試したことはありません。
> sw_vers
ProductName: macOS
ProductVersion: 15.3.2
BuildVersion: 24D81
> root --version
ROOT Version: 6.34.04
Built for macosxarm64 on Feb 10 2025, 12:15:34
From tags/6-34-04@6-34-04
> root-config --python-version
3.13.2
> root-config --cxxstandard
17
> python --version
Python 3.13.2
> g++ --version
Apple clang version 16.0.0 (clang-1600.0.26.6)
Target: arm64-apple-darwin24.3.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
準備
実行する前に以下のコマンドを実行してください。ROOTだけならbinにパスが通っていれば良いのですが、PyROOTを使うためにはPYTHONPATHの設定が必要になります。
> source thisroot.sh
Windows用にはthisroot.batがあります。
テスト用C++プログラムの作成
テスト用なので通常はヘッダーファイル無しでもOKなのですが、PyROOTはclassや関数の定義情報などはヘッダーファイルを読み込ませるだけで良いのでヘッダーファイルとプログラム本体を分けて作成します。
作成する機能
- クラス
- namespace
- double型の加算機能だけを持ったクラスにし、templateは使わない
- 構造体、配列、列挙型
- 通常の関数
- グローバル変数
- クラス外定義の演算子(operator+)
namespace myname {
class Double{
public:
double _x;
public:
Double():_x(0.0){}
Double(double x):_x(x){}
Double& add(double x);
Double& add(const Double &x);
Double operator+(double x) const;
Double operator+(const Double& x) const;
Double& operator+=(double x);
Double& operator+=(const Double& x);
double operator()()const;
static Double add(double x, double y);
};
}
myname::Double operator+(double, const myname::Double&);
enum CALC_METHOD{
ADD,
SUBTRACT
};
struct calc {
double num[2];
CALC_METHOD method;
};
extern struct calc g_calc;
double calculate();
double calculate(const struct calc&);
-
struct calc
は構造体で内部に配列をもつ -
struct calc g_calc
はグローバル変数 -
myname::Double operator+(double,myname::Double)
はDoubleクラスではdouble+Double
の演算ができないため、これで定義 -
double calculate()
はグローバル変数のstruct calc g_calc
を使って計算
次に実装です。
#include "double.h"
myname::Double& myname::Double::add(double x) {
_x += x;
return *this;
}
myname::Double& myname::Double::add(const myname::Double& x) {
_x += x._x;
return *this;
}
myname::Double myname::Double::operator+(double x) const{
return myname::Double(x + _x);
}
myname::Double myname::Double::operator+(const myname::Double& x) const {
return myname::Double(x._x + _x);
}
myname::Double& myname::Double::operator+=(double x) {
_x += x;
return *this;
}
myname::Double& myname::Double::operator+=(const myname::Double & x) {
_x += x._x;
return *this;
}
double myname::Double::operator()() const{
return _x;
}
myname::Double operator+(double x, const myname::Double& y) {
return y+x;
}
myname::Double myname::Double::add(double x, double y) {
return myname::Double(x+y);
}
struct calc g_calc;
double calculate() {
return calculate(g_calc);
}
double calculate(const struct calc& arg) {
if (arg.method == ADD)
return arg.num[0] + arg.num[1];
if (arg.method == SUBTRACT)
return arg.num[0] - arg.num[1];
return 0;
}
動的リンクライブラリの作成
> g++ double.cc -shared -o libdouble.so `root-config --cflags`
ちなみにroot-config --cflags
を見てみると
> root-config --cflags
-stdlib=libc++ -pthread -std=c++17 -m64 -I/opt/homebrew/Cellar/root/6.34.04/include/root
root-config
が面倒なら、今回のプログラムは-std=c++17
だけ指定しても大丈夫です。
Pythonからのテスト用C++プログラムにアクセス
Pythonのテスト用プログラムを作って試します。
# PyROOTをインポート
import ROOT
# C++のヘッダーファイルを読み込む
ROOT.gInterpreter.ProcessLine('#include "double.h"')
# 動的リンクライブラリをロードする
ROOT.gSystem.Load("./libdouble.so")
a = ROOT.myname.Double(123.0) # namespaceは.でつなげる
print("a =", a()) # double operator()()ではDoubleをdoubleに変換する
a.add(1.0)
print("a =", a())
b = a + 100 # Double operator+(double)
print("b =", b())
c = a + b # Double operator+(Double)
print("c =", c())
d = (-100.0) + c # Double operator+(double, Double)
print("d =", d())
e = ROOT.myname.Double.add(1.0, 2.0) # static Double add(double, double)
print("e =", e())
clc = ROOT.calc() # 構造体のインスタンス化
clc.num[0] = 2.0 # 配列は普通に[]が使える
clc.num[1] = 3.0
clc.method = ROOT.ADD # 列挙型
ret = ROOT.calculate(clc)
print("ret =", ret)
ROOT.g_calc.num[0] = 3.5
ROOT.g_calc.num[1] = 10
ROOT.g_calc.method = ROOT.SUBTRACT
ret = ROOT.calculate() # g_calcを使って計算
print("ret =", ret)
実行とその結果
> python double.py
a = 123.0
a = 124.0
b = 224.0
c = 348.0
d = 248.0
e = 3.0
ret = 5.0
ret = -6.5
期待通りの出力がされました。
PythonからC++の標準ライブラリのstd::vectorとそれをソートするstd::sortを使ってみる
標準ライブラリは特になにもしなくても使えます。
import ROOT
v = ROOT.std.vector[int]() # std::vector<int>のインスタンス化
v.push_back(1)
v.push_back(5)
v.push_back(3)
print(v)
ROOT.std.sort(v.begin(), v.end())
print(v)
ROOT.std.vector[int]
はROOT.std.vector[ROOT.int]
とも書けます。前者のintはPythonの型指定です。
実行結果は
> python sort1.py
{ 1, 5, 3 }
{ 1, 3, 5 }
ちゃんとソートされています。
Compare関数を指定する。
doubleの型にしようとしたがPythonにはfloatはあるがdoubleはなさそうなのでROOT.doubleを使った。
import ROOT
def ascend(x:ROOT.double, y:ROOT.double)->bool:
return x < y
def descend(x:ROOT.double, y:ROOT.double)->bool:
return x > y
v = ROOT.std.vector[ROOT.double]() # std::vector<double>のインスタンス化
v.push_back(1)
v.push_back(2)
v.push_back(1.3)
v.push_back(1.8)
print(v)
ROOT.std.sort(v.begin(), v.end(), ascend)
print(v)
ROOT.std.sort(v.begin(), v.end(), descend)
print(v)
Compare関数は型ヒントをつけないとエラーになります。
> python sort2.py
{ 1.0000000, 2.0000000, 1.3000000, 1.8000000 }
{ 1.0000000, 1.3000000, 1.8000000, 2.0000000 }
{ 2.0000000, 1.8000000, 1.3000000, 1.0000000 }
期待通りにソートされました。
次にPythonの関数に型を指定しないときはROOTを使ってC++の型定義ができます。
import ROOT
ROOT.gInterpreter.Declare("""
typedef std::function<bool(double,double)> compfunc;
""")
def ascend(x, y):
return x < y
v = ROOT.std.vector[ROOT.double]() # std::vector<double>のインスタンス化
v.push_back(1)
v.push_back(2)
v.push_back(1.3)
v.push_back(1.8)
print(v)
ROOT.std.sort(v.begin(), v.end(), ROOT.compfunc(ascend))
print(v)
print()
print(ascend)
print(ROOT.compfunc(ascend))
ROOT.gInterpreter.Declare
にC++のコードが記述できます。
> python sort3.py
{ 1.0000000, 2.0000000, 1.3000000, 1.8000000 }
{ 1.0000000, 1.3000000, 1.8000000, 2.0000000 }
<function ascend at 0x11c65a660>
<cppyy.gbl.std.function<bool(double,double)> object at 0x6000031a0ee0>
ちゃんとソートがされて、型も指定通りになっているのが確認できます。
PythonからCの関数を使ってみる (2025/3/27 追記)
簡単な加算の関数を作ります。ここではヘッダーファイルを用意します。
double add_double(double, double);
#include "ctest.h"
double add_double(double x, double y) {
return x + y;
}
動的リンクライブラリの作成
> gcc ctest.c -shared -o libctest.so
テスト用Python
import ROOT
ROOT.gInterpreter.Declare("""
#include "ctest.h"
""")
ROOT.gSystem.Load("./libctest.so")
print(ROOT.add_double(100, 123))
実行
> python ctest.py
IncrementalExecutor::executeFunction: symbol '_Z10add_doubledd' unresolved while linking symbol '__cf_12'!
You are probably missing the definition of add_double(double, double)
Maybe you need to load the corresponding shared library?
-1.0
やはり、そのままではC言語の関数を呼ぶことはできませんでした。
次にextern "C"
を試します。
import ROOT
ROOT.gInterpreter.Declare("""
extern "C" {
#include "ctest.h"
}
""")
ROOT.gSystem.Load("./libctest.so")
print(ROOT.add_double(100, 123))
実行
> python ctest.py
223.0
extern "C"
で無事に成功しました。
終わりに
以外と簡単に使えると思いました。C言語もextern "C"
で使えることが確認できたので良かったです。
PyROOTのC++との連携には下記サイトのcppyyが使われているようです。