thrustで3引数以上のtransformを実行する方法

[GPGPU Advent Calendar](http://atnd.org/events/34539)6日目です！
あっちに書いた通り私自身は全く情報系の人間ではないですしプログラマーじゃないので、期待はしないでください（という前書き。

で、あんまり抽象的なこと書けないので、タイトルみたいな実務的なことを書きます。
[thrustのサンプル](https://github.com/thrust/thrust/blob/master/examples/arbitrary_transformation.cu)があるので、それを見てもらって分かる方はそれを読んでください。

## 1. やりたいこと
計算力学やってると、（相対）位置ベクトルやら速度ベクトルやらの大きさが欲しい時があります。つまり、

```cpp:Length2.cpp
#include<iostream>

// 2次元ベクトルCPU
void Length2()
{
	// 要素数
	const int N = 5;

	// x, y方向成分
	double x[N] = {0, 1, 2, 3, 4};
	double y[N] = {1, 2, 3, 4, 5};

	// 各ベクトルの大きさ
	double length[N];

	// 大きさを計算
	for(int i = 0; i < N; i++)
	{
		// √(x^2 + y^2 + z^2)
		length[i] = std::sqrt(x[i]*x[i] + y[i]*y[i]);
	}

	// 結果を表示
	/*
	* 1.00, 2.24, 3.61, 5.00, 6.40ぐらい
	*/
	std::cout << "2次元ベクトルCPU" << std::endl;
	for(int i = 0; i < N; i++)
	{
		std::cout << i << ": " << length[i] << std::endl;
	}
}
```

ってな処理です。

## 2. じゃあ
これをthrustによるGPGPU by CUDAで並列化するには

```cpp:Length2Thrust.cpp
#include<iostream>

#include<thrust/device_vector.h>
#include<thrust/transform.h>

// 2次元ベクトルの大きさを取得する
struct GetLength2 : public thrust::binary_function<const double, const double, double>
{
	__host__ __device__
	double operator()(const double& x, const double& y) const
	{
		return std::sqrt(x*x + y*y);
	}
} getLength2;

// 2次元ベクトルThrust
void Length2Thrust()
{
	// 要素数
	const int N = 5;

	// x, y方向成分
	double x[N] = {0, 1, 2, 3, 4};
	double y[N] = {1, 2, 3, 4, 5};

	// 各ベクトルの大きさ
	double length[N];

	// デバイスの配列を生成
	thrust::device_vector<double> xVector(x, x + N);
	thrust::device_vector<double> yVector(y, y + N);
	thrust::device_vector<double> lengthVector(N);

	// 大きさを計算
	thrust::transform(xVector.begin(), xVector.begin() + N,
		yVector.begin(),
		lengthVector.begin(),
		getLength2);
	thrust::copy_n(lengthVector.begin(), N, length);

	// 結果を表示
	/*
	* 1.00, 2.24, 3.61, 5.00, 6.40ぐらい
	*/
	std::cout << "2次元ベクトルThrust" << std::endl;
	for(int i = 0; i < N; i++)
	{
		std::cout << i << ": " << length[i] << std::endl;
	}
}
```

と、[`thrust::binary_function`構造体](http://thrust.github.com/doc/structthrust_1_1binary__function.html)を継承した関数オブジェクトを使って演算子を定義したのち、[`thrust::transform`関数](http://thrust.github.com/doc/group__transformations.html#gaa1a7f2a2a6b01891e5ca6b6380409cf8)を使えばできます。

## 3. これもしたい
ただ、この`thrust::transform`関数なんですが、2変数以外にも1変数を引数に取ることができるんですが、**3変数以上がない**んですよね。
となると、ベクトルが3次元になった場合、

```cpp:Length3
#include<iostream>

// 3次元ベクトルCPU
void Length3()
{
	// 要素数
	const int N = 5;

	// x, y, z方向成分
	double x[N] = {0, 1, 2, 3, 4};
	double y[N] = {1, 2, 3, 4, 5};
	double z[N] = {2, 3, 4, 5, 6};

	// 各ベクトルの大きさ
	double length[N];

	// 大きさを計算
	for(int i = 0; i < N; i++)
	{
		// √(x^2 + y^2 + z^2)
		length[i] = std::sqrt(x[i]*x[i] + y[i]*y[i] + z[i]*z[i]);
	}

	// 結果を表示
	/*
	* 2.23, 3.74, 5.39, 7.07, 7.77ぐらい
	*/
	std::cout << "3次元ベクトルCPU" << std::endl;
	for(int i = 0; i < N; i++)
	{
		std::cout << i << ": " << length[i] << std::endl;
	}
}
```

に使えません。

## 4. じゃあどうするの
ということで、これが本題なんですが、**3変数以上を使う場合は、タプルを使って複数の引数をまとめる必要**があります。

```cpp:Length3Thrust.cpp
#include<iostream>

#include<thrust/device_vector.h>
#include<thrust/tuple.h>
#include<thrust/transform.h>
#include<thrust/iterator/zip_iterator.h>

// 3次元ベクトル（のタプル）
typedef thrust::tuple<double, double, double> Double3;

// 2次元ベクトルの大きさを取得する
struct GetLength3 : public thrust::unary_function<const Double3, double>
{
	__host__ __device__
	double operator()(const Double3& v) const
	{
		double x = v.get<0>();
		double y = v.get<1>();
		double z = v.get<2>();

		return std::sqrt(x*x + y*y + z*z);
	}
} getLength3;

// 3次元ベクトルThrust
void Length3Thrust()
{
	// 要素数
	const int N = 5;

	// x, y, z方向成分
	double x[N] = {0, 1, 2, 3, 4};
	double y[N] = {1, 2, 3, 4, 5};
	double z[N] = {2, 3, 4, 5, 6};

	// 各ベクトルの大きさ
	double length[N];

	// デバイスの配列を生成
	thrust::device_vector<double> xVector(x, x + N);
	thrust::device_vector<double> yVector(y, y + N);
	thrust::device_vector<double> zVector(z, z + N);
	thrust::device_vector<double> lengthVector(N);

	// タプルを作って、そのタプルのイテレーターを作成
	auto double3Tuple = thrust::make_tuple(xVector.begin(), yVector.begin(), zVector.begin());
	auto double3Iterator = thrust::make_zip_iterator(double3Tuple);
	
	// 大きさを計算
	thrust::transform(double3Iterator, double3Iterator + N,
		lengthVector.begin(),
		getLength3);
	thrust::copy_n(lengthVector.begin(), N, length);

	// 結果を表示
	/*
	* 2.23, 3.74, 5.39, 7.07, 7.77ぐらい
	*/
	std::cout << "3次元ベクトルCPU" << std::endl;
	for(int i = 0; i < N; i++)
	{
		std::cout << i << ": " << length[i] << std::endl;
	}
}
```

thrustにおいてタプルは[`thrust::tuple`クラス](http://thrust.github.com/doc/classthrust_1_1tuple.html)で表現できます。後々面倒なので冒頭（9行目）で型定義しておきます。テンプレートの型は「第1引数の型、第2引数の型、第3引数の型・・・」です（10コまで用意されているっぽい）。
実際に引数を取り出すには、[`thrust::get`関数](http://thrust.github.com/doc/group__tuple.html#ga4512fb8e91a457663ce15cb72b5ccd9e)を（テンプレート引数に引数番号を指定して）呼び出す必要がありますが、関数にしなくてもメソッド形式でも呼び出せるので、素直に`double first = tuple.get<0>();`とかしましょう。

このタプルを作るには、コンストラクタを呼び出すより、[`thrust::make_tuple`](http://thrust.github.com/doc/group__tuple.html#gacf485e2c89cd1736daef7123b8e5bdc6)を呼び出したほうがいいです。
タプルを作ったら、タプルのイテレーター（厳密には違うんですが）を[`thrust::make_zip_iterator`関数](http://thrust.github.com/doc/group__fancyiterator.html#ga3a68093f82072d1375ca8b921b550f1e)を使って[`thrust::zip_iterator`](http://thrust.github.com/doc/classthrust_1_1zip__iterator.html)として作ります。

あとはそのzip_iteratorをtransformの入力引数として入れてやれば完成です。

## 5. 応用
複数の値を返すこともできます。あんまり使い所ない気もしますが。

## 6. まとめ
という感じで、thrust便利すぎてOpenCLに戻れない私がいます。

アドベントカレンダーの方まだ人数全然埋まってないところを見ると、あと2-3回書かなきゃいけなそうなので、次回もあったらまたthrust書きます（と言いつつ違うこと書きそう。

明日、2012年12月7日の分は[@tmasda](https://twitter.com/tmasda)さんが書いてくれます！

## A. レポジトリ
全部使える版を[githubのレポジトリ](https://github.com/aokomoriuta/ThrustVector)にも置いておきますね。

## B. 余談
この情報、日本語でなくて英語で調べようとしたんですが、2つがbinary、1つがunaryは知ってたんですが、3つとか知らなくて迷いました。trinaryというらしいです。4つはquanaryらしいです。この情報の方が役に立ちそうです！