Nクイーン問題(63)第七章 並列処理 対称解除法 ビットボード NVIDIA CUDA編


【参考リンク】Nクイーン問題 過去記事一覧はこちらから

エイト・クイーンのプログラムアーカイブ
Bash、Lua、C、Java、Python、CUDAまで!
https://github.com/suzukiiichiro/N-Queens

CUDAセットアップ

手順など

今どきは少なくなったわけですが、CUDAをインストールから始めたい人はこちら

EC2 G4インスタンスのAmazon Linux 2にNVIDIA CUDAをインストールしてみた
https://dev.classmethod.jp/articles/install-nvidia-cuda-on-ec2-g4-amazon-linux-2/

今どきは、GPUインスタンスを使えば最初からnvccが実行できます。

AWSのGPUインスタンスでCUDAを動かす
https://qiita.com/navitime_tech/items/0a8073347c21800f1cad

料金やインスタンスの選定、起動までが詳しく書かれているページはこちら

AWS/EC2でGPUマシンを起動してみる
https://motomura-kazushi.net/2021/08/15/aws-ec2%E3%81%A7gpu%E3%83%9E%E3%82%B7%E3%83%B3%E3%82%92%E8%B5%B7%E5%8B%95%E3%81%97%E3%81%A6%E6%B7%B1%E5%B1%A4%E5%AD%A6%E7%BF%92%E3%82%92%E5%9B%9E%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B/

確認方法

GPUインスタンスでCUDAが使えるようになったかな?と、確認するには以下のコマンドを実行します。

$ nvcc --version

以下の内容が表示されればCUDAは実行可能な状態にあります。

NQueens2$ nvcc --version
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2023 NVIDIA Corporation
Built on Tue_Jul_11_02:31:28_PDT_2023
Cuda compilation tools, release 12.2, V12.2.128
Build cuda_12.2.r12.2/compiler.33053471_0
NQueens2$

よくありがちな質問ですが、topコマンドでは、GPUインスタンスの負荷を調べることはできませんので、以下のコマンドでGPUインスタンスのCPU不可やメモリ使用状況を確認します。

$ watch nvidia-smi

ミラー CUDA部分についての解説

実行

実行は以下の4種類です。

1.シングルスレッドで再帰

// 再帰 対称解除法
void symmetry_R(unsigned int size,struct local* l)
$ nvcc 04CUDA_Symmetry_BitBoard.cu && ./a.out -r

2.シングルスレッドで非再帰

// 非再帰 対称解除法
void symmetry_NR(unsigned int size,struct local* l)
$ nvcc 04CUDA_Symmetry_BitBoard.cu && ./a.out -c

3.シングルスレッドのGPU
こちらはInitCUDA()を通過後、以下のメソッドを呼び出します。
シングルスレッドで動作します。

// GPU 対称解除法 -g の実行時のみ呼び出されます
__host__ __device__
void GPU_symmetry_R(unsigned int size,struct local* hostLocal)
$ nvcc 04CUDA_Symmetry_BitBoard.cu && ./a.out -g

3の必要性は、4を開発している間、問題点を局所化するためです。
3の解がきちんと出力していれば、他の箇所に問題があると特定することが目的です。

4.マルチスレッドのGPU
こちらはInitCUDA()を通過後、以下のメソッドを呼び出します。
マルチスレッドで動作します。

// GPU -n ビットボードの実行 角にQがある・ないの分岐を行う
void BitBoard_build(const unsigned int size,int STEPS)
$ nvcc 04CUDA_Symmetry_BitBoard.cu && ./a.out -n

次の項では、4について具体的に説明します。

CUDA ビットボード メモ

  for(l.BOUND1=2;l.BOUND1<size-1;l.BOUND1++){
  for(l.BOUND1=1,l.BOUND2=size-1-1;l.BOUND1<l.BOUND2;l.BOUND1++,l.BOUND2--){

のfor文の単位で複数回GPUが実行される
そのため、BOUND1,BOUND2,TOPBIT,ENDBIT,LASTMASK,SIDEMASKはGPU内では同じ値になる

STEPS数がGPU 1回でできる最大数なので、STEPSまでノード(GPUに渡す、LEFT,DOWN,RIGHT,board)が溜まったら、)が溜まったら息継ぎできに1回GPUを実行する

BitBoardによる対称解除法 説明

以下、4のCUDA実行に限定して関数の説明を行います。

CUDA 初期化

bool InitCUDA()

CUDAデバイスが搭載されているか、GPUの数などを確認します。
GPUが搭載されていない場合は、returnします。

GPU -n ビットボードの実行 角にQがある・ないの分岐を行う

void BitBoard_build(const unsigned int size,int STEPS)

ボードの最上段角にQがある場合は以下の関数を呼び出します。

    BitBoard_backTrack1G(size,2,(left|bit)<<1,(down|bit),(right|bit)>>1,&l);

ボードの最上段角にQがない場合は以下の関数を呼び出します。

    BitBoard_backTrack2G(size,1,bit<<1,bit,bit>>1,&l);

C言語版もBash版もPython版もそうですが、ロジックはこれまでと全く変わりません。

GPU -n Qが角にある

void BitBoard_backTrack1G(const unsigned int size,unsigned int row,unsigned int _left,unsigned int _down,unsigned int _right,struct local* l)

ボードの最上部角にQがある場合に、このメソッドがGPUカーネル実行に必要な変数や変数のメモリ領域を確保します。

GPU -n Qが角にない

void BitBoard_backTrack2G(const unsigned int size,unsigned int row,unsigned int _left,unsigned int _down,unsigned int _right,struct local* l)

ボードの最上部角にQがない場合に、このメソッドがGPUカーネル実行に必要な変数や変数のメモリ領域を確保します。

GPU -n Qが角にない場合のバックトラック内の再帰処理をカーネルで行う

__global__
void BitBoard_cuda_kernel_b2(unsigned int size,unsigned int mark,unsigned int* _down,unsigned int* _left,unsigned int* _right,unsigned int* _total,unsigned int* _unique,unsigned long _cond,unsigned int* board,unsigned int _row,unsigned int BOUND1,unsigned int BOUND2,unsigned int SIDEMASK,unsigned int LASTMASK,unsigned int TOPBIT,unsigned int ENDBIT)

GPUで処理を行う極めて高速に繰り返し実行される再帰処理メソッド

GPU -n Qが角にある場合のバックトラック内の再帰処理をカーネルで行う

__global__
void BitBoard_cuda_kernel_b1(const unsigned int size,unsigned int mark,unsigned int* _down,unsigned int* _left,unsigned int* _right,unsigned int* _total,unsigned int* _unique,unsigned long _cond,unsigned int _row,unsigned int BOUND1)

GPUで処理を行う極めて高速に繰り返し実行される再帰処理メソッド

GPU -n 対称解除法

__device__ 
int BitBoard_symmetryOps(unsigned int size,unsigned int* board,unsigned int BOUND1,unsigned int BOUND2,unsigned int TOPBIT,unsigned int ENDBIT)

対称解除法の肝となるメソッド
この部分に関しては、事項で詳細に説明します。

対象解除法について

まず、Nが小さな盤面で考えていきます。
以下、順に見て理解を深めてもらえればと思います。

##対象解除法について(N2版)
ひとつの解には、盤面を90度・180度・270度回転、及びそれらの鏡像の合計8個の対称解が存在します。

  原型   90度  180度   270度
  12   41   34   23
  43   32   21   14
  
      上の行を左右反転                     
  21   14   43   32
  34   23   12   41

上図左上がユニーク解です。

  原型(ユニーク解)
  12 
  43 

1行目はユニーク解を90度、180度、270度回転したものです。

  原型   90度  180度   270度
  12   41   34   23
  43   32   21   14

2行目は1行目のそれぞれを左右反転したものです。

      上の行を左右反転                     
  21   14   43   32
  34   23   12   41

2行目はユニーク解を左右反転、対角反転、上下反転、逆対角反転したものとも解釈できます。
ただし、回転・線対称な解もあるので注意が必要です。

対称的な解を除去し、ユニーク解のみを求める方法はいくつかあります。
ここでは、一つの解には8つのパターンが存在することを覚えておいてください。

対象解を導き出す2つの方法

対象解を導き出す方法として、
1.解が見つかるたびに回転・対象をチェック
2.解のすべてを保存して、保存された解の回転・対象をチェック

最小解選択法
解がひとつみつかるとすべての対称解を生成、 状態を数値とみなして最も小さいもののみを解とする方法。
最も最適と言われています。

最小選択法よりも劣る方法
解全てを保存しておき、新しい解が発見されるたびに対称形が無いかどうかを調べる方法。
保存領域を必要とするし、比較時に対称形を生成する必要があります。

対象解除法(N5版)

全探索によって得られたある1つの解が、回転・反転などによる本質的に変わることのない変換によって他の解と同型となるものが存在する場合、それを別の解とはしないとする解の数え方で得られる解を「ユニーク解」といいます。

ユニーク解とは、全解の中から回転・反転などによる変換によって同型になるものどうしをグループ化することを意味しています。

グループ1:
+-+-+-+-+-+  +-+-+-+-+-+ 
| | | |Q| |  | |Q| | | |
+-+-+-+-+-+  +-+-+-+-+-+
|Q| | | | |  | | | | |Q|
+-+-+-+-+-+  +-+-+-+-+-+
| | |Q| | |  | | |Q| | |
+-+-+-+-+-+  +-+-+-+-+-+
| | | | |Q|  |Q| | | | |
+-+-+-+-+-+  +-+-+-+-+-+
| |Q| | | |  | | | |Q| |
+-+-+-+-+-+  +-+-+-+-+-+

グループ2:
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  
| | | | |Q|  |Q| | | | |  | | |Q| | |  | | |Q| | |  | | | |Q| |  | |Q| | | |  |Q| | | | |  | | | | |Q|  
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  
| | |Q| | |  | | |Q| | |  |Q| | | | |  | | | | |Q|  | |Q| | | |  | | | |Q| |  | | | |Q| |  | |Q| | | |  
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  
|Q| | | | |  | | | | |Q|  | | | |Q| |  | |Q| | | |  | | | | |Q|  |Q| | | | |  | |Q| | | |  | | | |Q| |  
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  
| | | |Q| |  | |Q| | | |  | |Q| | | |  | | | |Q| |  | | |Q| | |  | | |Q| | |  | | | | |Q|  |Q| | | | |  
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  
| |Q| | | |  | | | |Q| |  | | | | |Q|  |Q| | | | |  |Q| | | | |  | | | | |Q|  | | |Q| | |  | | |Q| | |  
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  

ユニーク解はその「個数のみ」に着目され、この解はユニーク解であり、この解はユニーク解ではないという定まった判定方法はありません。
ユニーク解であるかどうかの判断はユニーク解の個数を数える目的の為だけにあります。
どのような定義をしたとしてもユニーク解の個数それ自体は変わりません。

Nクイーン問題は、正方形のボードで形成されるので、回転・反転による変換パターンはぜんぶで8通りあります。

だからといって「全解数=ユニーク解数×8」と単純にはいきません。
ひとつのグループの要素数が必ず8個あるとは限らないのです。

N=5の例では2つのグループがあり、要素数が2個のグループ、要素数が8個のグループがあります。
結論から言うと、N=5の全解は10個で、ユニーク解は2個です。

ユニーク解を判定するための定義

各行のクイーンが左から何番目にあるかを調べて、最上段の行(row)から下の行(row)へ順番に列挙します。
そしてそれをN桁の数値として見た場合に「最大値」となるものをユニーク解とします。
このN桁の数を以後は「ユニーク判定値」と呼ぶことにします。

 0 1 2 3 4 
+-+-+-+-+-+  
| | | | |Q|    4
+-+-+-+-+-+  
| | |Q| | |    2 
+-+-+-+-+-+  
|Q| | | | |    0  ----> 4 2 0 3 1 (ユニーク判定値)
+-+-+-+-+-+             数が大きい方をユニークとみなす 
| | | |Q| |    3
+-+-+-+-+-+  
| |Q| | | |    1 
+-+-+-+-+-+  

 0 1 2 3 4     左右反転!
+-+-+-+-+-+  
|Q| | | | |    0
+-+-+-+-+-+  
| | |Q| | |    2 
+-+-+-+-+-+  
| | | | |Q|    4  ----> 0 2 4 1 3 
+-+-+-+-+-+            数が小さいのでユニーク解とはしません 
| |Q| | | |    1
+-+-+-+-+-+  
| | | |Q| |    3 
+-+-+-+-+-+  

探索によって得られたある1つの解(オリジナル)がユニーク解であるかどうかを判定するには、「8通りの変換を試み、その中でオリジナルのユニーク判定値が最小であるかを調べる」ことになります。

N2の場合の8通りの変換

  原型   90度  180度   270度
  12   41   34   23
  43   32   21   14
  
      上の行を左右反転                     
  21   14   43   32
  34   23   12   41

結論から先にいえば、ユニーク解とは成り得ないことが明確なパターンを探索中に切り捨てる「枝刈り」を組み込むことにより、3通りの変換を試みるだけでユニーク解の判定が可能になります。

枝刈り

先ず最上段の行(row)のクイーンの位置に着目します。
その位置が左半分の領域にあればユニーク解には成り得ません。
何故なら左右反転によって得られるパターンのユニーク判定値の方が確実に小さくなるからです。

 0 1 2 3 4     
+-+-+-+-+-+  
|Q| | | | |    0
+-+-+-+-+-+  
| | |Q| | |    2 
+-+-+-+-+-+  
| | | | |Q|    4  ----> 0 2 4 1 3 
+-+-+-+-+-+            数が小さいのでユニークとはしません
| |Q| | | |    1
+-+-+-+-+-+  
| | | |Q| |    3 
+-+-+-+-+-+  

 0 1 2 3 4     左右反転!
+-+-+-+-+-+  
| | | | |Q|    4
+-+-+-+-+-+  
| | |Q| | |    2 
+-+-+-+-+-+  
|Q| | | | |    0  ----> 4 2 0 3 1 (ユニーク判定値)
+-+-+-+-+-+             数が大きい方をユニークとみなす 
| | | |Q| |    3
+-+-+-+-+-+  
| |Q| | | |    1 
+-+-+-+-+-+  

Nが奇数の場合に中央にあった場合はどうでしょう。

+-+-+-+-+-+  
| | |Q| | |    2
+-+-+-+-+-+  
| | | | | |      
+-+-+-+-+-+  
| | | | | |       
+-+-+-+-+-+             
| | | | | |     
+-+-+-+-+-+  
| | | | | |      
+-+-+-+-+-+  

これもユニーク解には成り得ません。
何故なら仮に中央にあった場合、それがユニーク解であるためには少なくとも他の3辺におけるクイーンの位置も中央になければならず、それは互いの効き筋にあたるので解になりえないからです。

+-+-+-+-+-+  
| | |Q| | |    
+-+-+-+-+-+  
| | | | | |      
+-+-+-+-+-+  
|Q| | | |Q|       
+-+-+-+-+-+             
| | | | | |     
+-+-+-+-+-+  
| | |Q| | |      
+-+-+-+-+-+  

最上段の行のクイーンの位置は中央を除く右側の領域に限定されます。(ただし、N ≧ 2)

+-+-+-+-+-+  
| | | |Q|Q|    
+-+-+-+-+-+  
| | | | | |      
+-+-+-+-+-+  
| | | | | |       
+-+-+-+-+-+             
| | | | | |     
+-+-+-+-+-+  
| | | | | |      
+-+-+-+-+-+  

次にその中でも一番右端(右上の角)にクイーンがある場合を考えてみます。

+-+-+-+-+-+  
| | | | |Q|    
+-+-+-+-+-+  
| | | | | |      
+-+-+-+-+-+  
| | | | | |       
+-+-+-+-+-+             
| | | | | |     
+-+-+-+-+-+  
| | | | | |      
+-+-+-+-+-+  

他の3つの角にクイーンを置くことはできないので、

+-+-+-+-+-+  
|Q| | | |Q|    
+-+-+-+-+-+  
| | | | | |      
+-+-+-+-+-+  
| | | | | |       
+-+-+-+-+-+             
| | | | | |     
+-+-+-+-+-+  
|Q| | | |Q|      
+-+-+-+-+-+  

ユニーク解であるかどうかを判定するには、右上角から左下角を通る斜軸で反転させたパターンとの比較だけになります。

+-+-+-+-+-+  
| | | | |Q|    
+-+-+-+-+-+  
| | | |/| |      
+-+-+-+-+-+  
| | |/| | |       
+-+-+-+-+-+             
| |/| | | |     
+-+-+-+-+-+  
|/| | | | |      
+-+-+-+-+-+  

突き詰めれば、上から2行目(row1)のクイーンの位置が右から何番目にあるかと、

+-+-+-+-+-+  
| | | | |Q|    
+-+-+-+-+-+  
| |Q| |/| |  ←
+-+-+-+-+-+  
| | |/| | |       
+-+-+-+-+-+             
| |/| | | |     
+-+-+-+-+-+  
|/| | | | |      
+-+-+-+-+-+  

右から2列目のクイーンの位置が上から何番目にあるかを比較するだけで判定することができます。

+-+-+-+-+-+  
| | | | |Q|    
+-+-+-+-+-+  
| |Q| |/| |      
+-+-+-+-+-+  
| | |/| | |       
+-+-+-+-+-+             
| |/| |Q| |  ←    
+-+-+-+-+-+  
|/| | | | |      
+-+-+-+-+-+  

この2つの値が同じになることはないからです。

       3 0
+-+-+-+-+-+  
| | | | |Q| 0
+-+-+-+-+-+  
| |Q| |/| | 3    上から2行目のクイーンの位置が右から4番目にある。
+-+-+-+-+-+  
| | |/| | |       
+-+-+-+-+-+             
| |/| |Q| |  ←  右から2列目のクイーンの位置が上から4番目にある。
+-+-+-+-+-+      しかし、互いの効き筋にあたるのでこれは有り得ない。 
|/| | | | |      
+-+-+-+-+-+  

結局、再帰探索中において下図の X への配置を禁止する枝刈りを入れておけば、得られる解は総てユニーク解であることが保証されます。

+-+-+-+-+-+  
| | | |X|Q| 
+-+-+-+-+-+  
| |Q| |X| | 
+-+-+-+-+-+  
| | | |X| |       
+-+-+-+-+-+             
| | | |Q| | 
+-+-+-+-+-+ 
| | | | | |      
+-+-+-+-+-+  

次に右端以外にクイーンがある場合を考えてみます。
オリジナルがユニーク解であるためには先ず下図の X への配置は禁止されます。
よって、その枝刈りを先ず入れておきます。

+-+-+-+-+-+-+-+-+  
|X|X| | | |Q|X|X| 
+-+-+-+-+-+-+-+-+  
|X| | | | | | |X| 
+-+-+-+-+-+-+-+-+  
| | | | | | | | |       
+-+-+-+-+-+-+-+-+             
| | | | | | | | | 
+-+-+-+-+-+-+-+-+ 
| | | | | | | | |      
+-+-+-+-+-+-+-+-+  
| | | | | | | | |
+-+-+-+-+-+-+-+-+
|X| | | | | | |X|
+-+-+-+-+-+-+-+-+
|X|X| | | | |X|X|
+-+-+-+-+-+-+-+-+

次にクイーンの利き筋を辿っていくと、結局、オリジナルがユニーク解ではない可能性があるのは、下図の A,B,C の位置のどこかにクイーンがある場合に限られます。

+-+-+-+-+-+-+-+-+  
|X|X| | | |Q|X|X| 
+-+-+-+-+-+-+-+-+  
|X| | | |x|x|x|X| 
+-+-+-+-+-+-+-+-+  
|C| | |x| |x| |x|       
+-+-+-+-+-+-+-+-+             
| | |x| | |x| | | 
+-+-+-+-+-+-+-+-+ 
| |x| | | |x| | |      
+-+-+-+-+-+-+-+-+  
|x| | | | |x| |A|
+-+-+-+-+-+-+-+-+
|X| | | | |x| |X|
+-+-+-+-+-+-+-+-+
|X|X|B| | |x|X|X|
+-+-+-+-+-+-+-+-+

従って、90度回転、180度回転、270度回転の3通りの変換パターンだけを調べれはよいことになります。

ユニーク解を数える

これまでの考察はユニーク解の個数を求めるためのものでした。
全解数を求めるにはユニーク解を求めるための枝刈りを取り除いて全探索する必要があります。
したがって探索時間を犠牲にしてしまうことになります。
そこで「ユニーク解の個数から全解数を導いてしまおう」という試みが考えられます。
これは、左右反転(前章のミラー)によるパターンの探索を省略して最後に結果を2倍するというアイデアの拡張版といえるものです。
そしてそれを実現させるには「ユニーク解が属するグループの要素数はいくつあるのか」という考察が必要になってきます。

最初に、クイーンが右上角にあるユニーク解を考えます。

+-+-+-+-+-+  
| | | | |Q| 
+-+-+-+-+-+  
| | | | | | 
+-+-+-+-+-+  
| | | | | |       
+-+-+-+-+-+             
| | | | | | 
+-+-+-+-+-+ 
| | | | | |      
+-+-+-+-+-+  

斜軸で反転したパターンがオリジナルと同型になることは有り得ないことと(×2)、

+-+-+-+-+-+  +-+-+-+-+-+ 
| | | | |Q|  | | | | |Q| 
+-+-+-+-+-+  +-+-+-+-+-+ 
| | |Q|/| |  | | | |/| | 
+-+-+-+-+-+  +-+-+-+-+-+ 
| | |/| | |  | | |/|Q| | 
+-+-+-+-+-+  +-+-+-+-+-+            
| |/| | | |  | |/| | | | 
+-+-+-+-+-+  +-+-+-+-+-+ 
|/| | | | |  |/| | | | |     
+-+-+-+-+-+  +-+-+-+-+-+ 

右上角のクイーンを他の3つの角に写像させることができるので(×4)、

+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 
| | | | |Q|  | | | | | |  | | | | | |  |Q| | | | | 
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 
| | | | | |  | | | | | |  | | | | | |  | | | | | | 
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 
| | | | | |  | | | | | |  | | | | | |  | | | | | |      
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+            
| | | | | |  | | | | | |  | | | | | |  | | | | | | 
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 
| | | | | |  | | | | |Q|  |Q| | | | |  | | | | | |     
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 

このユニーク解が属するグループの要素数は必ず8個(=2×4)になります。

次に、クイーンが右上角以外にある場合は少し複雑になります。

(1) 90度回転させてオリジナルと同型になる場合、さらに90度回転(オリジナルから180度回転)させても、さらに90度回転(オリジナルから270度回転)させてもオリジナルと同型になる。

90度回転させても同じ場合は

+-+-+-+-+-+  +-+-+-+-+-+ 
| | | |Q| |  | | | |Q| | 
+-+-+-+-+-+  +-+-+-+-+-+ 
|Q| | | | |  |Q| | | | | 
+-+-+-+-+-+  +-+-+-+-+-+ 
| | |Q| | |  | | |Q| | | 
+-+-+-+-+-+  +-+-+-+-+-+            
| | | | |Q|  | | | | |Q| 
+-+-+-+-+-+  +-+-+-+-+-+ 
| |Q| | | |  | |Q| | | |     
+-+-+-+-+-+  +-+-+-+-+-+ 

さらに90度回転(原型から180度回転)させても

                 さらに90度回転
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 
| | | |Q| |  | | | |Q| |  | | | |Q| | 
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 
|Q| | | | |  |Q| | | | |  |Q| | | | | 
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 
| | |Q| | |  | | |Q| | |  | | |Q| | | 
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+            
| | | | |Q|  | | | | |Q|  | | | | |Q| 
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 
| |Q| | | |  | |Q| | | |  | |Q| | | |     
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 

さらに90度回転(原型から270度回転)させても同じ!

                               さらに90度回転
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 
| | | |Q| |  | | | |Q| |  | | | |Q| |  | | | |Q| | 
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 
|Q| | | | |  |Q| | | | |  |Q| | | | |  |Q| | | | | 
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 
| | |Q| | |  | | |Q| | |  | | |Q| | |  | | |Q| | | 
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+            
| | | | |Q|  | | | | |Q|  | | | | |Q|  | | | | |Q| 
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 
| |Q| | | |  | |Q| | | |  | |Q| | | |  | |Q| | | |     
+-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+  +-+-+-+-+-+ 


(2) 90度回転させてオリジナルと異なる場合は、270度回転させても必ずオリジナルとは異なる。
ただし、180度回転させた場合はオリジナルと同型になることも有り得る。

ユニーク数から全解を求める

1.クイーンが右上角にある場合、ユニーク解が属するグループの要素数は必ず8個(=2×4)

2.クイーンが右上角以外にある場合、
(1) 90度回転させてオリジナルと同型になる場合、さらに90度回転(オリジナルから180度回転)させても、
さらに90度回転(オリジナルから270度回転)させてもオリジナルと同型になる。
こちらに該当するユニーク解が属するグループの要素数は、左右反転させたパターンを加えて2個しかありません。

(2) 90度回転させてオリジナルと異なる場合は、270度回転させても必ずオリジナルとは異なる。
ただし、180度回転させた場合はオリジナルと同型になることも有り得る。
こちらに該当するユニーク解が属するグループの要素数は、180度回転させて同型になる場合は4個(左右反転×縦横回転)

(3)180度回転させてもオリジナルと異なる場合は、8個(左右反転×縦横回転×上下反転)

1の場合は、解の数x8
2−(1)の場合は、解の数x2
2−(2)の場合は、解の数x4
2−(3)の場合は、解の数x8
それぞれのグループの解の数にグループのパターン数をかけ合わせた数の総計が全解となります。
ひとつひとつのユニーク解が上のどの種類に該当するのかを調べることにより全解数を計算で導き出すことができるのです。

対象解除法 4つの関数

対象解除法のソースは4つの関数で構成されています。

1.printRecord()
解を発見するたびにボードレイアウトを表示します。
以降、新しい枝刈りポイントを発見するためにも使います。
board[]を使ってQの位置を確認するのに役立ちます。

2.symmetryOps()
対象解除を行うロジックメソッドです。
以下のロジックが同梱されています。

1.クイーンが右上角にある場合、ユニーク解が属するグループの要素数は必ず8個(=2×4)
2.クイーンが右上角以外にある場合、

(1) 90度回転させてオリジナルと同型になる場合、さらに90度回転(オリジナルから180
度回転)させても、さらに90度回転(オリジナルから270度回転)させてもオリジナルと同
型になる。
こちらに該当するユニーク解が属するグループの要素数は、左右反転させたパター ンを
加えて2個しかありません。
(2) 90度回転させてオリジナルと異なる場合は、270度回転させても必ずオリジナルとは
  異なる。ただし、180度回転させた場合はオリジナルと同型になることも有り得る。
こちらに該当するユニーク解が属するグループの要素数は、180度回転させて同型になる
  場合は4個(左右反転×縦横回転)
(3)180度回転させてもオリジナルと異なる場合は、8個(左右反転×縦横回転×上下反転)

3.backTrack()
再帰ロジックで作られています。
右上角にQが配置されているかいないかで判定処理が分岐されます。
その判定処理に従って、見合った枝刈りが行われます。

4.symmetry()
本体メソッドです。
こちらのメソッドでは、Qが角に配置されている場合、そうでない場合を分岐して3のbackTrack()に渡します。

symmetry()メソッド

角にQがある時の処理

symmetry()メソッドは、Qが角にあるかどうかを判定し、適切にbackTrack()に処理を渡します。

では上から順番に見ていきます。
とても大切なことなのですが、グローバル変数の初期化を行います。

  TOTAL=UNIQUE=COUNT2=COUNT4=COUNT8=0;

角にQがある時のボードのイメージは以下のとおりです。

  角にQがある時の処理
    +-+-+-+-+-+  
    | | | | |Q| 
    +-+-+-+-+-+  
    | | | | | | 
    +-+-+-+-+-+  
    | | | | | |       
    +-+-+-+-+-+             
    | | | | | | 
    +-+-+-+-+-+ 
    | | | | | |      
    +-+-+-+-+-+  

MASKについてですが、こちらはrow全体にビットを立てて配置済みとするフィルタのことです。

N5の場合、 10進数では31、2進数では 11111 となります。

MASK=(1<<size)-1;
    +-+-+-+-+-+  
    |M|M|M|M|M|  MASK=(1<<size)-1
    +-+-+-+-+-+  $(( (1<<5)-1 )):31
    | | | | | |  $(( 2#11111 )) 
    +-+-+-+-+-+  $ 31
    | | | | | |       
    +-+-+-+-+-+             
    | | | | | | 
    +-+-+-+-+-+ 
    | | | | | |      
    +-+-+-+-+-+  

bashでは以下のようにすると2進数を10進数に変換して表示することができます。

$ echo $(( 2#11111 ))
$ 31

TOPBITは左上角にビットを立てたものです。

  TOPBIT=1<<(size-1);
    +-+-+-+-+-+  
    |T| | | | |  TOPBIT=1<<(size-1);
    +-+-+-+-+-+  $(( (1<<(5-1) )):16 
    | | | | | |  $(( 2#10000 )) 
    +-+-+-+-+-+  $ 16 
    | | | | | |       
    +-+-+-+-+-+             
    | | | | | | 
    +-+-+-+-+-+ 
    | | | | | |      
    +-+-+-+-+-+  

ENDBITはTOPBITの1つ右にビットを立てたものです。現在はまだ初期値0のままです。
後述しますがENDBITは、

ENDBIT=TOPBIT>>1;

となります。

  ENDBIT=LASTMASK=SIDEMASK=0;
    は何も置かない(なにもはじまってない)
    +-+-+-+-+-+  
    |T|E| | | |  0#01000
    +-+-+-+-+-+  
    | | | | | | 
    +-+-+-+-+-+  
    | | | | | |       
    +-+-+-+-+-+             
    | | | | | | 
    +-+-+-+-+-+ 
    | | | | | |      
    +-+-+-+-+-+  

配列BOUND1とBOUND2を初期化して
Qを右上角に配置します。

  BOUND1=2; 
  BOUND2=0;
    +-+-+-+-+-+  右角から1つ左から開始
    | | | |B| |  BOUND1は右から左へシフト
    +-+-+-+-+-+   
    | | | | | | 
    +-+-+-+-+-+  
    | | | | | |       
    +-+-+-+-+-+             
    | | | | | | 
    +-+-+-+-+-+ 
    | | | | | |      
    +-+-+-+-+-+  
    +-+-+-+-+-+  左角から1つ右から開始
    | |B| | | |  BOUND2は左から右へシフトします。
    +-+-+-+-+-+  
    | | | | | | 
    +-+-+-+-+-+  
    | | | | | |       
    +-+-+-+-+-+             
    | | | | | | 
    +-+-+-+-+-+ 
    | | | | | |      
    +-+-+-+-+-+  

ここはすごく重要で、row[0]の1というのは右上の角のことを意味します。ですので、「Qを右上角に配置した」ということになります。

  board[0]=1;       # Qを右上角に配置

ここで、rowは2行目の処理に入ります。
というのも、1行目は右上角にQを配置したので次に進んだわけです。

その後、以下の行は「2行目の真ん中にQを配置する」という意味となります。

      bit=1<<BOUND1;

イメージでは以下のとおりです。

       の場合、BOUND1は2と3になる
        1<<BOUND1  行目は真ん中に置く
        +-+-+-+-+-+  
        | | | | |Q|  1#00001
        +-+-+-+-+-+  
        | | |B| | |  BOUND1=2 4#00100
        +-+-+-+-+-+  $(( 1<<2 ))
        | | | | | |  $ 4
        +-+-+-+-+-+  1->2->4               
        | | | | | | 
        +-+-+-+-+-+ 
        | | | | | |      
        +-+-+-+-+-+  

さらに、処理の中でBOUND1は左に1つシフトします。

        1<<BOUND1  行目は真ん中の次に置く
        +-+-+-+-+-+  
        | | | | |Q|  1#00001
        +-+-+-+-+-+  
        | |B| | | |  BOUND1=3 8#01000
        +-+-+-+-+-+  $(( 1<<3 ))
        | | | | | |  $ 8
        +-+-+-+-+-+  1->2->4->8             
        | | | | | | 
        +-+-+-+-+-+ 
        | | | | | |      
        +-+-+-+-+-+  

角にQがない時の処理

TOPBITは先程も書いたとおり、左上角にビットを立てた状態を示します。

  TOPBIT=1<<(size-1)
    +-+-+-+-+-+  
    |T| | | | | TOPBIT=1<<(size-1)
    +-+-+-+-+-+ $(( 2#10000 )) 
    | | | | | | $ 16 
    +-+-+-+-+-+  
    | | | | | |       
    +-+-+-+-+-+             
    | | | | | | 
    +-+-+-+-+-+ 
    | | | | | |      
    +-+-+-+-+-+  

ENDBITは、TOPBITの1つ右にビットを立てた状態です。

  ENDBIT=TOPBIT>>1
    +-+-+-+-+-+  
    |T|E| | | | ENDBIT=TOPBIT>>1
    +-+-+-+-+-+ $(( 2#01000 )) 
    | | | | | | $ 8
    +-+-+-+-+-+  
    | | | | | |       
    +-+-+-+-+-+             
    | | | | | | 
    +-+-+-+-+-+ 
    | | | | | |      
    +-+-+-+-+-+  

SIDEMASKは行の両サイドの角にビットを立てた状態を示します。

  SIDEMASK=TOPBIT|1
    +-+-+-+-+-+  
    |S| | | |S| SIDEMASK=TOPBIT|1
    +-+-+-+-+-+ $(( 2#10001 )) 
    | | | | | | $ 17
    +-+-+-+-+-+  
    | | | | | |       
    +-+-+-+-+-+             
    | | | | | | 
    +-+-+-+-+-+ 
    | | | | | |      
    +-+-+-+-+-+  

LASTMASKもSIDEMASK同様左右の角のビットを立てた状態を示します。ただ使いみちとしては、上記の状態でも使えるわけですが、

  LASTMASK=TOPBIT|1
    +-+-+-+-+-+  
    |L| | | |L| LASTMASK=TOPBIT|1
    +-+-+-+-+-+ $(( 2#10001 )) 
    | | | | | | $ 17
    +-+-+-+-+-+  
    | | | | | |       
    +-+-+-+-+-+             
    | | | | | | 
    +-+-+-+-+-+ 
    | | | | | |      
    +-+-+-+-+-+  

こういうシチュエーションで使います。

  LASTMASK=TOPBIT|1
    +-+-+-+-+-+  
    | | | | | | LASTMASK=TOPBIT|1
    +-+-+-+-+-+ $(( 2#10001 )) 
    | | | | | | $ 17
    +-+-+-+-+-+  
    | | | | | |       
    +-+-+-+-+-+             
    | | | | | | 
    +-+-+-+-+-+ 
    |L| | | |L|      
    +-+-+-+-+-+  

ENDBITは左角の1つ手前から右へシフトするビットです。

    ENDBIT=ENDBIT>>1
      +-+-+-+-+-+  
      |T|E| | | | ENDBIT=TOPBIT>>1
      +-+-+-+-+-+ $(( 2#01000 )) 
      | | | | | | $ 8
      +-+-+-+-+-+  
      | | | | | |       
      +-+-+-+-+-+             
      | | | | | | 
      +-+-+-+-+-+ 
      | | | | | |      
      +-+-+-+-+-+  
           
      +-+-+-+-+-+  
      |T| |E| | | ENDBIT=ENDBIT>>1
      +-+-+-+-+-+ $(( 2#00100 )) 
      | | | | | | $ 4
      +-+-+-+-+-+  
      | | | | | |       
      +-+-+-+-+-+             
      | | | | | | 
      +-+-+-+-+-+ 
      | | | | | |      
      +-+-+-+-+-+  

LASTMASKに代入されるビットは以下のイメージとなります。使いみちはLASTMASKと似ていますが、

    +-+-+-+-+-+  
    |L|L| |L|L| LASTMASK=LASTMASK<<1 | LASTMASK | LASTMASK>>1
    +-+-+-+-+-+ $(( 2#11011 )) 
    | | | | | | $ 27
    +-+-+-+-+-+  
    | | | | | |       
    +-+-+-+-+-+             
    | | | | | | 
    +-+-+-+-+-+ 
    | | | | | |      
    +-+-+-+-+-+  

こういったシチュエーションにも使います。

    +-+-+-+-+-+  
    | | | | | | LASTMASK=LASTMASK<<1 | LASTMASK | LASTMASK>>1
    +-+-+-+-+-+ $(( 2#11011 )) 
    | | | | | | $ 27
    +-+-+-+-+-+  
    | | | | | |       
    +-+-+-+-+-+             
    | | | | | | 
    +-+-+-+-+-+ 
    |L|L| |L|L|      
    +-+-+-+-+-+  

backTrack()メソッド

角にQがある時の処理

このメソッドでは、角にQがある場合の処理、ない場合の処理の分岐があり、その分岐の中で、((COUNT8++))であったり、symmetryOpsといった対象解除の処理へ進んだり、はたまた「枝刈り」をしたりと、濃厚なメソッドです。

では、順番に見ていきます。

ここは、Qが角にある場合の処理で、rowが最終行までたどり着いた(配置できた)場合の処理となります。グループの要素数は必ず8あるわけですので、あとから8倍するCOUNT8をインクリメントします。

      .クイーンが右上角にある場合、ユニーク解が属する
      グループの要素数は必ず(×)
      if(row==(size-1)){
        if(bitmap){
          board[row]=bitmap;
          COUNT8++;
        }
      }else{
  次は、Qが角にある場合の処理で、
  上から2行目のクイーンの位置が左から何番目にあるかと、
  右から2列目のクイーンの位置が上から何番目にあるかを、
  比較するだけで判定します。
  具体的には、2行目と2列目の位置を数値とみなし、
  2行目<2列目という条件を課せばよい
  という枝仮ルールを実現している処理となります。
			}else{
				if(row<BOUND1){   //枝刈り
					bitmap=bitmap|2;
					bitmap=bitmap^2;
				}
				while(bitmap){
        : '
        上から行目のクイーンの位置が左から何番目にあるかと、
        右から列目のクイーンの位置が上から何番目にあるかを、
        比較するだけで判定します。
        具体的には、行目と列目の位置を数値とみなし、
        行目<列目という条件を課せばよい
        結論: 以下の図では、を枝刈りを入れる
          
          +-+-+-+-+-+  
          | | | |X|Q| 
          +-+-+-+-+-+  
          | |Q| |X| |  8(左から数えて
          +-+-+-+-+-+  
          | | | |X| |       
          +-+-+-+-+-+             
          | | | |Q| |  8(上から数えて 
          +-+-+-+-+-+ 
          | | | | | |      
          +-+-+-+-+-+  
        ';
      fi

たったこれだけの2行ですごいですね。

         bitmap=bitmap|2;
         bitmap=bitmap^2;

角にQがない時の処理

ここは少しわかりにくいですね。丁寧に説明文を入れましたので、じっくりと理解してみてください。

    }else{                            # Qが角にない
      : '
      オリジナルがユニーク解であるためには先ず、
      前提:symmetryOpsは回転・鏡像変換により得られる状態の
      ユニーク値を比較し最小のものだけがユニーク解となるようにしている。
      Qができるだけ右に置かれている方がユニーク値は小さい
      例えば1行目の2列目にQが置かれている方が3列目に置かれているより
      ユニーク値は小さくユニーク解に近い。
      1行目のクイーンの位置が同じなら2行目のクイーンの位置がより右の
      列におかれているものがユニーク値は小さくユニーク解に近い。

      下図の X への配置は禁止されます。
      Qの位置より右位置の8対象位置X)にクイーンを置くことはできない。
      置いた場合回転・鏡像変換したユニーク値が最小にならなくなり、symmetryOps
      で負けるので枝刈りをする


      1行目のクイーンが3列目に置かれている場合
      +-+-+-+-+-+-+-+-+  
      |X|X| | | |Q|X|X| 
      +-+-+-+-+-+-+-+-+  
      |X| | | | | | |X| 
      +-+-+-+-+-+-+-+-+  
      | | | | | | | | |       
      +-+-+-+-+-+-+-+-+             
      | | | | | | | | | 
      +-+-+-+-+-+-+-+-+ 
      | | | | | | | | |      
      +-+-+-+-+-+-+-+-+  
      | | | | | | | | |
      +-+-+-+-+-+-+-+-+
      |X| | | | | | |X|
      +-+-+-+-+-+-+-+-+
      |X|X| | | | |X|X|
      +-+-+-+-+-+-+-+-+

      1行目のクイーンが4列目に置かれている場合
      +-+-+-+-+-+-+-+-+  
      |X|X|X| |Q|X|X|X| 
      +-+-+-+-+-+-+-+-+  
      |X| | | | | | |X| 
      +-+-+-+-+-+-+-+-+  
      |X| | | | | | |X|       
      +-+-+-+-+-+-+-+-+             
      | | | | | | | | | 
      +-+-+-+-+-+-+-+-+ 
      | | | | | | | | |      
      +-+-+-+-+-+-+-+-+  
      |X| | | | | | |X|
      +-+-+-+-+-+-+-+-+
      |X| | | | | | |X|
      +-+-+-+-+-+-+-+-+
      |X|X|X| | |X|X|X|
      +-+-+-+-+-+-+-+-+

      プログラムではこの枝刈を上部サイド枝刈り、下部サイド枝刈り、最下段枝刈り
      3か所で行っている
      それぞれ、1,2,3の数字で表すと以下の通り

      1行目のクイーンが3列目に置かれている場合
      +-+-+-+-+-+-+-+-+  
      |X|X| | | |Q|X|X| 
      +-+-+-+-+-+-+-+-+  
      |1| | | | | | |1| 
      +-+-+-+-+-+-+-+-+  
      | | | | | | | | |       
      +-+-+-+-+-+-+-+-+             
      | | | | | | | | | 
      +-+-+-+-+-+-+-+-+ 
      | | | | | | | | |      
      +-+-+-+-+-+-+-+-+  
      | | | | | | | | |
      +-+-+-+-+-+-+-+-+
      |2| | | | | | |2|
      +-+-+-+-+-+-+-+-+
      |2|3| | | | |3|2|
      +-+-+-+-+-+-+-+-+
      1行目にXが残っているが当然Qの効き筋なので枝刈する必要はない
      ';
			if(row<BOUND1){
				bitmap=bitmap|SIDEMASK;
				bitmap=bitmap^SIDEMASK;
			}else{
				if(row==BOUND2){
					if((down&SIDEMASK)==0){
						return;
					}
					if( (down&SIDEMASK)!=SIDEMASK){
						bitmap=bitmap&SIDEMASK;
					}
				}
			}

symmetryOps()メソッド

ここもロジックを理解するところから初めて見ると良いと思います。
あまり細かいことを考えるよりも、大枠でどんなときにこのメソッドが呼ばれ、カウンターがいつインクリメントするのか、などを掴んだほうが良いと思います。

symmetryOps()では以下を実現しています。

2.クイーンが右上角以外にある場合、
(1) 90度回転させてオリジナルと同型になる場合、さらに90度回転(オリジナルか
ら180度回転)させても、さらに90度回転(オリジナルから270度回転)させてもオリ
ジナルと同型になる。
こちらに該当するユニーク解が属するグループの要素数は、左右反転させたパター
ンを加えて2個しかありません。
2.クイーンが右上角以外にある場合、
(2) 90度回転させてオリジナルと異なる場合は、270度回転させても必ずオリジナル
とは異なる。ただし、180度回転させた場合はオリジナルと同型になることも有り得
る。こちらに該当するユニーク解が属するグループの要素数は、180度回転させて同
型になる場合は4個(左右反転×縦横回転)
2.クイーンが右上角以外にある場合、
(3)180度回転させてもオリジナルと異なる場合は、8個(左右反転×縦横回転×上下反転)

クイーンの利き筋を辿っていくと、オリジナルがユニーク解ではない可能性があり、
それは下図の A,B,C の位置のどこかにクイーンがある場合に限られます。
symmetryOpsは、以下の図のA,B,CにQが置かれた場合にユニーク解かを判定します。
原型と、90,180,270回転させたもののユニーク値を比較します。

     0 1 2 3 4 
    +-+-+-+-+-+  
    | | | | |Q|    4
    +-+-+-+-+-+  
    | | |Q| | |    2 
    +-+-+-+-+-+  
    |Q| | | | |    0  ----> 4 2 0 3 1 (ユニーク判定値)
    +-+-+-+-+-+             数が大きい方をユニークとみなす 
    | | | |Q| |    3
    +-+-+-+-+-+  
    | |Q| | | |    1 
    +-+-+-+-+-+  

     0 1 2 3 4     左右反転!
    +-+-+-+-+-+  
    |Q| | | | |    0
    +-+-+-+-+-+  
    | | |Q| | |    2 
    +-+-+-+-+-+  
    | | | | |Q|    4  ----> 0 2 4 1 3 
    +-+-+-+-+-+            数が小さいのでユニーク解とはしません 
    | |Q| | | |    1
    +-+-+-+-+-+  
    | | | |Q| |    3 
    +-+-+-+-+-+  

Qができるだけ右に置かれている方がユニーク値は大きくなります。
例えば1行目の2列目にQが置かれている方が、
3列目に置かれているよりユニーク値は大きくユニーク解に近い。
1行目のクイーンの位置が同じなら2行目のクイーンの位置がより右の列におかれてい
るものがユニーク値は大きくユニーク解に近くなります。
それ以外はユニーク解なのでCOUNT8にする

   +-+-+-+-+-+-+-+-+  
   |X|X| | | |Q|X|X| 
   +-+-+-+-+-+-+-+-+  
   |X| | | |x|x|x|X| 
   +-+-+-+-+-+-+-+-+  
   |C| | |x| |x| |x|       
   +-+-+-+-+-+-+-+-+             
   | | |x| | |x| | | 
   +-+-+-+-+-+-+-+-+ 
   | |x| | | |x| | |      
   +-+-+-+-+-+-+-+-+  
   |x| | | | |x| |A|
   +-+-+-+-+-+-+-+-+
   |X| | | | |x| |X|
   +-+-+-+-+-+-+-+-+
   |X|X|B| | |x|X|X|
   +-+-+-+-+-+-+-+-+

Aの場合 右90度回転 board[BOUND2]==1
Bの場合 右180度回転 board[size-1]==ENDBIT
Cの場合 右270度回転 board[BOUND1]==TOPBIT

CUDA ソースコード

ソースコードは以下のとおりです。

/**
 *
 * bash版対称解除法のC言語版のGPU/CUDA移植版
 *
 詳しい説明はこちらをどうぞ
 https://suzukiiichiro.github.io/search/?keyword=Nクイーン問題

 非再帰でのコンパイルと実行
 $ nvcc -O3 -arch=sm_61 04CUDA_Symmetry_BitBoard.cu && ./a.out -c

 再帰でのコンパイルと実行
 $ nvcc -O3 -arch=sm_61 04CUDA_Symmetry_BitBoard.cu && ./a.out -r

 GPU で並列処理せずに実行
 $ nvcc -O3 -arch=sm_61 04CUDA_Symmetry_BitBoard.cu && ./a.out -g

 GPU で並列処理で実行(ビットボード)
 $ nvcc -O3 -arch=sm_61 -m64 -ptx -prec-div=false 04CUDA_Symmetry_BitBoard.cu && POCL_DEBUG=all ./a.out -n ;

 CUDAのリアルタイム監視
 $ watch nvidia-smi

対称解除法 GPUビットボード
 N:            Total           Unique      dd:hh:mm:ss.ms
 4:                2                1      00:00:00:00.13
 5:               10                2      00:00:00:00.00
 6:                4                1      00:00:00:00.00
 7:               40                6      00:00:00:00.00
 8:               92               12      00:00:00:00.00
 9:              352               46      00:00:00:00.00
10:              724               92      00:00:00:00.01
11:             2680              341      00:00:00:00.01
12:            14200             1787      00:00:00:00.01
13:            73712             9233      00:00:00:00.03
14:           365596            45752      00:00:00:00.03
15:          2279184           285053      00:00:00:00.03
16:         14772512          1846955      00:00:00:00.05
17:         95815104         11977939      00:00:00:00.24
18:        666090624         83263591      00:00:00:01.75
19:       4968057848        621012754      00:00:00:14.52
20:      39029188884       4878666808      00:00:02:08.10
21:     314666222712      39333324973      00:00:19:56.19
22:    2691008701644     336376244042      00:03:08:14.53
23:   24233937684440    3029242658210      01:06:56:07.12
24:  227514171973736   28439272956934      13:05:38:39.13
*
  for(l.BOUND1=2;l.BOUND1<size-1;l.BOUND1++){
  for(l.BOUND1=1,l.BOUND2=size-1-1;l.BOUND1<l.BOUND2;l.BOUND1++,l.BOUND2--){
  のfor文の単位で複数回GPUが実行される
  そのため、BOUND1,BOUND2,TOPBIT,ENDBIT,LASTMASK,SIDEMASKはGPU内では同じ値になる

  STEPS数がGPU 1回でできる最大数なので、STEPSまでノード(GPUに渡す、LEFT,DOWN,RIGHT,board)が溜まったら、)が溜まったら息継ぎできに1回GPUを実行する
  

*/
#include <iostream>
#include <vector>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <math.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <device_launch_parameters.h>
#define MAX 27
#define THREAD_NUM 96
// システムによって以下のマクロが必要であればコメントを外してください。
//#define UINT64_C(c) c ## ULL
//
// グローバル変数
unsigned long TOTAL=0;
unsigned long UNIQUE=0;
//GPU で使うローカル構造体
typedef struct local
{
  unsigned int BOUND1,BOUND2;
  unsigned int TOPBIT,ENDBIT,SIDEMASK,LASTMASK;
  unsigned long board[MAX];
  unsigned long COUNT2,COUNT4,COUNT8,TOTAL,UNIQUE;
  unsigned int STEPS;
}local;
// CPU 再帰/非再帰共通 対称解除法
void symmetryOps(unsigned int size,struct local* l)
{
  /**
  2.クイーンが右上角以外にある場合、
  (1) 90度回転させてオリジナルと同型になる場合、さらに90度回転(オリジナルか
  ら180度回転)させても、さらに90度回転(オリジナルから270度回転)させてもオリ
  ジナルと同型になる。
  こちらに該当するユニーク解が属するグループの要素数は、左右反転させたパター
  ンを加えて2個しかありません。
  */
  if(l->board[l->BOUND2]==1){
    unsigned int ptn;
    unsigned int own;
    for(ptn=2,own=1;own<size;++own,ptn<<=1){
      unsigned int bit;
      unsigned int you;
      for(bit=1,you=size-1;(l->board[you]!=ptn)&&l->board[own]>=bit;--you){
        bit<<=1;
      }
      if(l->board[own]>bit){
        return ;
      }
      if(l->board[own]<bit){
        break;
      }
    }//end for
    // 90度回転して同型なら180度回転しても270度回転しても同型である
    if(own>size-1){
      l->COUNT2++;
      return ;
    }//end if
  }//end if
  /**
  2.クイーンが右上角以外にある場合、
    (2) 90度回転させてオリジナルと異なる場合は、270度回転させても必ずオリジナル
    とは異なる。ただし、180度回転させた場合はオリジナルと同型になることも有り得
    る。こちらに該当するユニーク解が属するグループの要素数は、180度回転させて同
    型になる場合は4個(左右反転×縦横回転)
   */
  //180度回転
  if(l->board[size-1]==l->ENDBIT){
    unsigned int you;
    unsigned int own;
    for(you=size-1-1,own=1;own<=size-1;++own,--you){
      unsigned int bit;
      unsigned int ptn;
      for(bit=1,ptn=l->TOPBIT;(ptn!=l->board[you])&&(l->board[own]>=bit);ptn>>=1){
        bit<<=1;
      }
      if(l->board[own]>bit){
        return ;
      }
      if(l->board[own]<bit){
        break;
      }
    }//end for
    //90度回転が同型でなくても180度回転が同型であることもある
    if(own>size-1){
      l->COUNT4++;
      return ;
    }
  }//end if
  /**
  2.クイーンが右上角以外にある場合、
    (3)180度回転させてもオリジナルと異なる場合は、8個(左右反転×縦横回転×上下反転)
  */
  //270度回転
  if(l->board[l->BOUND1]==l->TOPBIT){
    unsigned int ptn;
    unsigned int own;
    unsigned int you;
    unsigned int bit;
    for(ptn=l->TOPBIT>>1,own=1;own<=size-1;++own,ptn>>=1){
      for(bit=1,you=0;(l->board[you]!=ptn)&&(l->board[own]>=bit);++you){
        bit<<=1;
      }
      if(l->board[own]>bit){
        return ;
      }
      if(l->board[own]<bit){
        break;
      }
    }//end for
  }//end if
  l->COUNT8++;
}
/**
  CPU -c
  */
// 非再帰 角にQがないときのバックトラック
void symmetry_backTrack_NR(unsigned int size,unsigned int row,unsigned int _left,unsigned int _down,unsigned int _right,struct local *l)
{
  unsigned int mask=(1<<size)-1;
  unsigned int down[size];
  unsigned int left[size];
  unsigned int right[size];
  unsigned int bitmap[size];
  left[row]=_left;
  down[row]=_down;
  right[row]=_right;
  bitmap[row]=mask&~(left[row]|down[row]|right[row]);
  while(row>0){
    if(bitmap[row]>0){
      if(row<l->BOUND1){ //上部サイド枝刈り
        bitmap[row]|=l->SIDEMASK;
        bitmap[row]^=l->SIDEMASK;
      }else if(row==l->BOUND2){ //下部サイド枝刈り
        if((down[row]&l->SIDEMASK)==0){
          row--; 
        }
        if((down[row]&l->SIDEMASK)!=l->SIDEMASK){
          bitmap[row]&=l->SIDEMASK;
        }
      }
      unsigned int save_bitmap=bitmap[row];
      unsigned int bit=-bitmap[row]&bitmap[row];
      bitmap[row]^=bit;
      l->board[row]=bit; //Qを配置
      if((bit&mask)!=0){
        if(row==(size-1)){
          if( (save_bitmap&l->LASTMASK)==0){
            symmetryOps(size,l);  //対称解除法
          }
          row--;
        }else{
          unsigned int n=row++;
          left[row]=(left[n]|bit)<<1;
          down[row]=(down[n]|bit);
          right[row]=(right[n]|bit)>>1;
          bitmap[row]=mask&~(left[row]|down[row]|right[row]);
        }
      }else{
        row--;
      }
    }else{
      row--;
    }
  }//end while
}
// 非再帰 角にQがあるときのバックトラック
void symmetry_backTrack_corner_NR(unsigned int size,unsigned int row,unsigned int _left,unsigned int _down,unsigned int _right,struct local *l)
{
  unsigned int mask=(1<<size)-1;
  unsigned int bit=0;
  unsigned int down[size];
  unsigned int left[size];
  unsigned int right[size];
  unsigned int bitmap[size];
  left[row]=_left;
  down[row]=_down;
  right[row]=_right;
  bitmap[row]=mask&~(left[row]|down[row]|right[row]);
  while(row>=2){
    if(row<l->BOUND1){
      // bitmap[row]=bitmap[row]|2;
      // bitmap[row]=bitmap[row]^2;
      bitmap[row]&=~2;
    }
    if(bitmap[row]>0){
      bit=-bitmap[row]&bitmap[row];
      bitmap[row]^=bit;
      if(row==(size-1)){
        l->COUNT8++;
        row--;
      }else{
        unsigned int n=row++;
        left[row]=(left[n]|bit)<<1;
        down[row]=(down[n]|bit);
        right[row]=(right[n]|bit)>>1;
        l->board[row]=bit; //Qを配置
        //クイーンが配置可能な位置を表す
        bitmap[row]=mask&~(left[row]|down[row]|right[row]);
      }
    }else{
      row--;
    }
  }//end while
}
// 非再帰 対称解除法
void symmetry_NR(unsigned int size,struct local* l)
{
  l->TOTAL=l->UNIQUE=l->COUNT2=l->COUNT4=l->COUNT8=0;
  unsigned int bit=0;
  l->TOPBIT=1<<(size-1);
  l->ENDBIT=l->SIDEMASK=l->LASTMASK=0;
  l->BOUND1=2;
  l->BOUND2=0;
  l->board[0]=1;
  while(l->BOUND1>1&&l->BOUND1<size-1){
    if(l->BOUND1<size-1){
      bit=1<<l->BOUND1;
      l->board[1]=bit;   //2行目にQを配置
      //角にQがあるときのバックトラック
      symmetry_backTrack_corner_NR(size,2,(2|bit)<<1,1|bit,(2|bit)>>1,l);
    }
    l->BOUND1++;
  }
  l->TOPBIT=1<<(size-1);
  l->ENDBIT=l->TOPBIT>>1;
  l->SIDEMASK=l->TOPBIT|1;
  l->LASTMASK=l->TOPBIT|1;
  l->BOUND1=1;
  l->BOUND2=size-2;
  while(l->BOUND1>0 && l->BOUND2<size-1 && l->BOUND1<l->BOUND2){
    if(l->BOUND1<l->BOUND2){
      bit=1<<l->BOUND1;
      l->board[0]=bit;   //Qを配置
      //角にQがないときのバックトラック
      symmetry_backTrack_NR(size,1,bit<<1,bit,bit>>1,l);
    }
    l->BOUND1++;
    l->BOUND2--;
    l->ENDBIT=l->ENDBIT>>1;
    l->LASTMASK=l->LASTMASK<<1|l->LASTMASK|l->LASTMASK>>1;
  }//ene while
  UNIQUE=l->COUNT2+l->COUNT4+l->COUNT8;
  TOTAL=l->COUNT2*2+l->COUNT4*4+l->COUNT8*8;
}
/**
  CPU -r
  */
// 再帰 角にQがないときのバックトラック
void symmetry_backTrack(unsigned int size,unsigned int row,unsigned int left,unsigned int down,unsigned int right,struct local* l)
{
  unsigned int mask=(1<<size)-1;
  unsigned int bitmap=mask&~(left|down|right);
  if(row==(size-1)){
    if(bitmap){
      if( (bitmap&l->LASTMASK)==0){
        l->board[row]=bitmap;  //Qを配置
        symmetryOps(size,l);    //対称解除
      }
    }
  }else{
    if(row<l->BOUND1){
      bitmap=bitmap|l->SIDEMASK;
      bitmap=bitmap^l->SIDEMASK;
    }else{
      if(row==l->BOUND2){
        if((down&l->SIDEMASK)==0){
          return;
        }
        if( (down&l->SIDEMASK)!=l->SIDEMASK){
          bitmap=bitmap&l->SIDEMASK;
        }
      }
    }
    while(bitmap){
      unsigned int bit=-bitmap&bitmap;
      bitmap=bitmap^bit;
      l->board[row]=bit;
      symmetry_backTrack(size,row+1,(left|bit)<<1,down|bit,(right|bit)>>1,l);
    }
  }
}
// 再帰 角にQがあるときのバックトラック
void symmetry_backTrack_corner(unsigned int size,unsigned int row,unsigned int left,unsigned int down,unsigned int right,struct local* l)
{
  unsigned int mask=(1<<size)-1;
  unsigned int bitmap=mask&~(left|down|right);
  unsigned int bit=0;
  if(row==(size-1)){
    if(bitmap){
      l->board[row]=bitmap;
      l->COUNT8++;
    }
  }else{
    if(row<l->BOUND1){   //枝刈り
      bitmap=bitmap|2;
      bitmap=bitmap^2;
    }
    while(bitmap){
      bit=-bitmap&bitmap;
      bitmap=bitmap^bit;
      l->board[row]=bit;   //Qを配置
      symmetry_backTrack_corner(size,row+1,(left|bit)<<1,down|bit,(right|bit)>>1,l);
    }
  }
}
// 再帰 対称解除法
void symmetry_R(unsigned int size,struct local* l)
{
  l->TOTAL=l->UNIQUE=l->COUNT2=l->COUNT4=l->COUNT8=0;
  unsigned int bit=0;
  l->TOPBIT=1<<(size-1);
  l->ENDBIT=l->LASTMASK=l->SIDEMASK=0;
  l->BOUND1=2;
  l->BOUND2=0;
  l->board[0]=1;
  while(l->BOUND1>1 && l->BOUND1<size-1){
    if(l->BOUND1<size-1){
      bit=1<<l->BOUND1;
      l->board[1]=bit;   //2行目にQを配置
      //角にQがあるときのバックトラック
      symmetry_backTrack_corner(size,2,(2|bit)<<1,1|bit,(2|bit)>>1,l);
    }
    l->BOUND1++;
  }//end while
  l->TOPBIT=1<<(size-1);
  l->ENDBIT=l->TOPBIT>>1;
  l->SIDEMASK=l->TOPBIT|1;
  l->LASTMASK=l->TOPBIT|1;
  l->BOUND1=1;
  l->BOUND2=size-2;
  while(l->BOUND1>0 && l->BOUND2<size-1 && l->BOUND1<l->BOUND2){
    if(l->BOUND1<l->BOUND2){
      bit=1<<l->BOUND1;
      l->board[0]=bit;   //Qを配置
      //角にQがないときのバックトラック
      symmetry_backTrack(size,1,bit<<1,bit,bit>>1,l);
    }
    l->BOUND1++;
    l->BOUND2--;
    l->ENDBIT=l->ENDBIT>>1;
    l->LASTMASK=l->LASTMASK<<1|l->LASTMASK|l->LASTMASK>>1;
  }//ene while
  UNIQUE=l->COUNT2+l->COUNT4+l->COUNT8;
  TOTAL=l->COUNT2*2+l->COUNT4*4+l->COUNT8*8;
}
/**
  GPU -g
  */
__device__
struct dlocal
{
  unsigned int BOUND1,BOUND2;
  unsigned int TOPBIT,ENDBIT,SIDEMASK,LASTMASK;
  unsigned long board[MAX];
  unsigned long COUNT2,COUNT4,COUNT8,TOTAL,UNIQUE;
}dlocal;
__device__ struct dlocal gdl[9999];
// GPU 対称解除法
__host__ __device__
long GPU_symmetryOps(unsigned int size,struct dlocal* l)
{
  /**
  2.クイーンが右上角以外にある場合、
  (1) 90度回転させてオリジナルと同型になる場合、さらに90度回転(オリジナルか
  ら180度回転)させても、さらに90度回転(オリジナルから270度回転)させてもオリ
  ジナルと同型になる。
  こちらに該当するユニーク解が属するグループの要素数は、左右反転させたパター
  ンを加えて2個しかありません。
  */
  if(l->board[l->BOUND2]==1){
    unsigned int ptn;
    unsigned int own;
    for(ptn=2,own=1;own<size;++own,ptn<<=1){
      unsigned int bit;
      unsigned int you;
      for(bit=1,you=size-1;(l->board[you]!=ptn)&& l->board[own]>=bit;--you){
        bit<<=1;
      }
      if(l->board[own]>bit){
        return 0;
      }
      if(l->board[own]<bit){
        break;
      }
    }//end for
    // 90度回転して同型なら180度回転しても270度回転しても同型である
    if(own>size-1){
      l->COUNT2++;
      return 2;
    }//end if
  }//end if
  /**
  2.クイーンが右上角以外にある場合、
    (2) 90度回転させてオリジナルと異なる場合は、270度回転させても必ずオリジナル
    とは異なる。ただし、180度回転させた場合はオリジナルと同型になることも有り得
    る。こちらに該当するユニーク解が属するグループの要素数は、180度回転させて同
    型になる場合は4個(左右反転×縦横回転)
   */
  //180度回転
  if(l->board[size-1]==l->ENDBIT){
    unsigned int you;
    unsigned int own;
    for(you=size-1-1,own=1;own<=size-1;++own,--you){
      unsigned int bit;
      unsigned int ptn;
      for(bit=1,ptn=l->TOPBIT;(ptn!=l->board[you])&&(l->board[own]>=bit);ptn>>=1){
        bit<<=1;
      }
      if(l->board[own]>bit){
        return 0;
      }
      if(l->board[own]<bit){
        break;
      }
    }//end for
    //90度回転が同型でなくても180度回転が同型であることもある
    if(own>size-1){
      l->COUNT4++;
      return 4;
    }
  }//end if
  /**
  2.クイーンが右上角以外にある場合、
    (3)180度回転させてもオリジナルと異なる場合は、8個(左右反転×縦横回転×上下反転)
  */
  //270度回転
  if(l->board[l->BOUND1]==l->TOPBIT){
    unsigned int ptn;
    unsigned int own;
    unsigned int you;
    unsigned int bit;
    for(ptn=l->TOPBIT>>1,own=1;own<=size-1;++own,ptn>>=1){
      for(bit=1,you=0;(l->board[you]!=ptn)&&(l->board[own]>=bit);++you){
        bit<<=1;
      }
      if(l->board[own]>bit){
        return 0;
      }
      if(l->board[own]<bit){
        break;
      }
    }//end for
  }//end if
  l->COUNT8++;
  return 8;
}
// GPU 角にQがないときのバックトラック
__host__ __device__
long GPU_symmetry_backTrack(unsigned int size,unsigned int row,unsigned int left,unsigned int down,unsigned int right,struct dlocal* l)
{
  unsigned long counter=0;
  unsigned int mask=(1<<size)-1;
  unsigned int bitmap=mask&~(left|down|right);
  if(row==(size-1)){
    if(bitmap){
      if( (bitmap& l->LASTMASK)==0){
        l->board[row]=bitmap;  //Qを配置
        counter+=GPU_symmetryOps(size,l);    //対称解除
      }
    }
  }else{
    if(row<l->BOUND1){
      bitmap=bitmap|l->SIDEMASK;
      bitmap=bitmap^l->SIDEMASK;
    }else{
      if(row==l->BOUND2){
        if((down&l->SIDEMASK)==0){
          return 0;
        }
        if( (down&l->SIDEMASK)!=l->SIDEMASK){
          bitmap=bitmap&l->SIDEMASK;
        }
      }
    }
    while(bitmap){
      unsigned int bit=-bitmap&bitmap;
      bitmap=bitmap^bit;
      l->board[row]=bit;
      counter+=GPU_symmetry_backTrack(size,row+1,(left|bit)<<1,down|bit,(right|bit)>>1,l);
    }
  }
  return counter;
}
// GPU 角にQがあるときのバックトラック
__host__ __device__
long GPU_symmetry_backTrack_corner(unsigned int size,unsigned int row,unsigned int left,unsigned int down,unsigned int right,struct dlocal* l)
{
  unsigned long counter=0;
  unsigned int mask=(1<<size)-1;
  unsigned int bitmap=mask&~(left|down|right);
  unsigned int bit=0;
  if(row==(size-1)){
    if(bitmap){
      l->board[row]=bitmap;
      l->COUNT8++;
      counter+=8;
    }
  }else{
    if(row<l->BOUND1){   //枝刈り
      bitmap=bitmap|2;
      bitmap=bitmap^2;
    }
    while(bitmap){
      bit=-bitmap&bitmap;
      bitmap=bitmap^bit;
      l->board[row]=bit;   //Qを配置
      counter+=GPU_symmetry_backTrack_corner(size,row+1,(left|bit)<<1,down|bit,(right|bit)>>1,l);
    }
  }
  return counter;
}
// GPU 対称解除法 -g の実行時のみ呼び出されます
__host__ __device__
void GPU_symmetry_R(unsigned int size,struct local* hostLocal)
{
  // GPU内部で使うための dlocal構造体
  struct dlocal l;
  l.TOTAL=l.UNIQUE=l.COUNT2=l.COUNT4=l.COUNT8=0;
  unsigned int bit=0;
  l.TOPBIT=1<<(size-1);
  l.ENDBIT=l.LASTMASK=l.SIDEMASK=0;
  l.BOUND1=2;
  l.BOUND2=0;
  l.board[0]=1;
  while(l.BOUND1>1 && l.BOUND1<size-1){
    if(l.BOUND1<size-1){
      bit=1<<l.BOUND1;
      l.board[1]=bit;   //2行目にQを配置
      //角にQがあるときのバックトラック
      GPU_symmetry_backTrack_corner(size,2,(2|bit)<<1,1|bit,(2|bit)>>1,&l);
    }
    l.BOUND1++;
  }//end while
  l.TOPBIT=1<<(size-1);
  l.ENDBIT=l.TOPBIT>>1;
  l.SIDEMASK=l.TOPBIT|1;
  l.LASTMASK=l.TOPBIT|1;
  l.BOUND1=1;
  l.BOUND2=size-2;
  while(l.BOUND1>0 && l.BOUND2<size-1 && l.BOUND1<l.BOUND2){
    if(l.BOUND1<l.BOUND2){
      bit=1<<l.BOUND1;
      l.board[0]=bit;   //Qを配置
      //角にQがないときのバックトラック
      GPU_symmetry_backTrack(size,1,bit<<1,bit,bit>>1,&l);
    }
    l.BOUND1++;
    l.BOUND2--;
    l.ENDBIT=l.ENDBIT>>1;
    l.LASTMASK=l.LASTMASK<<1|l.LASTMASK|l.LASTMASK>>1;
  }//ene while
  // 集計値は hostLocalへ代入
  hostLocal->UNIQUE=l.COUNT2+l.COUNT4+l.COUNT8;
  hostLocal->TOTAL=l.COUNT2*2+l.COUNT4*4+l.COUNT8*8;
}
/**
  CUDA13
  */
// GPU -n 対称解除法
__device__ 
int BitBoard_symmetryOps(unsigned int size,unsigned int* board,unsigned int BOUND1,unsigned int BOUND2,unsigned int TOPBIT,unsigned int ENDBIT)
{
  unsigned int own,ptn,you,bit;
  //90度回転
  if(board[BOUND2]==1){ own=1; ptn=2;
    while(own<=size-1){ bit=1; you=size-1;
      while((board[you]!=ptn)&&(board[own]>=bit)){ bit<<=1; you--; }
      if(board[own]>bit){ return 0; } else if(board[own]<bit){ break; }
      own++; ptn<<=1;
    }
    /** 90度回転して同型なら180度/270度回転も同型である */
    if(own>size-1){ return 2; }
  }
  //180度回転
  if(board[size-1]==ENDBIT){ own=1; you=size-1-1;
    while(own<=size-1){ bit=1; ptn=TOPBIT;
      while((board[you]!=ptn)&&(board[own]>=bit)){ bit<<=1; ptn>>=1; }
      if(board[own]>bit){ return 0; } else if(board[own]<bit){ break; }
      own++; you--;
    }
    /** 90度回転が同型でなくても180度回転が同型である事もある */
    if(own>size-1){ return 4; }
  }
  //270度回転
  if(board[BOUND1]==TOPBIT){ own=1; ptn=TOPBIT>>1;
    while(own<=size-1){ bit=1; you=0;
      while((board[you]!=ptn)&&(board[own]>=bit)){ bit<<=1; you++; }
      if(board[own]>bit){ return 0; } else if(board[own]<bit){ break; }
      own++; ptn>>=1;
    }
  }
  return 8; 
}
// GPU -n Qが角にある場合のバックトラック内の再帰処理をカーネルで行う
__global__
void BitBoard_cuda_kernel_b1(const unsigned int size,unsigned int mark,unsigned int* _down,unsigned int* _left,unsigned int* _right,unsigned int* _total,unsigned int* _unique,unsigned long _cond,unsigned int _row,unsigned int BOUND1)
{
  const unsigned int mask=(1<<size)-1;
  unsigned long total=0;
  unsigned int unique=0;
  int row=0;
  unsigned int bit;
  //
  //スレッド
  //
  //ブロック内のスレッドID
  const unsigned int tid=threadIdx.x;
  //グリッド内のブロックID
  const unsigned int bid=blockIdx.x;
  //全体通してのID
  const unsigned int idx=bid*blockDim.x+tid;
  //
  //シェアードメモリ
  //
  //sharedメモリを使う ブロック内スレッドで共有
  //10固定なのは現在のmask設定で
  //GPUで実行するのは最大10だから
  //THREAD_NUMはブロックあたりのスレッド数
  __shared__ unsigned int down[THREAD_NUM][10];
  down[tid][row]=_down[idx];
  __shared__ unsigned int left[THREAD_NUM][10];
  left[tid][row]=_left[idx];
  __shared__ unsigned int right[THREAD_NUM][10];
  right[tid][row]=_right[idx];
  __shared__ unsigned int bitmap[THREAD_NUM][10];
  bitmap[tid][row] =mask&~(down[tid][row]|left[tid][row]|right[tid][row]);
  __shared__ unsigned int sum[THREAD_NUM];
  __shared__ unsigned int usum[THREAD_NUM];
  //余分なスレッドは動かさない 
  //GPUはSTEPS数起動するが_cond以上は空回しする
  if(idx<_cond){
    //_down,_left,_rightの情報を
    //down,left,rightに詰め直す 
    //CPU で詰め込んだ t_はSTEPS個あるが
    //ブロック内ではブロックあたりのスレッド数に限定
    //されるので idxでよい
    //
    unsigned int bitmap_tid_row;
    unsigned int down_tid_row;
    unsigned int left_tid_row;
    unsigned int right_tid_row;
    while(row>=0){
      bitmap_tid_row=bitmap[tid][row];
      down_tid_row=down[tid][row];
      left_tid_row=left[tid][row];
      right_tid_row=right[tid][row];
      if(bitmap_tid_row==0){
        row--;
      }else{
        /**11 枝刈り**********/
        if(row+_row<BOUND1) {
          bitmap_tid_row=bitmap[tid][row]&=~2; // bm|=2; bm^=2; (bm&=~2と同等)
        }  
        //クイーンを置く
        //置く場所があるかどうか
        bitmap[tid][row]
          ^=bit
          =(-bitmap_tid_row&bitmap_tid_row);       
        if((bit&mask)!=0){
          //最終行?最終行から1個前の行まで
          //無事到達したら 加算する
          if(row+1==mark){
            //ホストに戻す配列にTOTALを入れる
            //スレッドが1つの場合は配列は1個
            unique++; 
            total+=8;   //対称解除で得られた解数を加算
            //}
            row--;
          }else{
            int rowP=row+1;
            down[tid][rowP]=down_tid_row|bit;
            left[tid][rowP]=(left_tid_row|bit)<<1;
            right[tid][rowP]=(right_tid_row|bit)>>1;
            bitmap[tid][rowP]=mask&~(down[tid][rowP]|left[tid][rowP]|right[tid][rowP]);
            row++;
          }
        }else{
          //置く場所がなければ1個上に
          row--;
        }
      }
    }
    //最後sum[tid]に加算する
    sum[tid]=total;
    usum[tid]=unique;
  }else{
    //_cond未満は空回しするのでtotalは加算しない
    sum[tid]=0;
    usum[tid]=0;
  } 
  //__syncthreads()でブロック内のスレッド間の同期
  //全てのスレッドが__syncthreads()に辿り着くのを待つ
  __syncthreads();
  if(tid<64&&tid+64<THREAD_NUM){
    sum[tid]+=sum[tid+64];
    usum[tid]+=usum[tid+64];
  }
  __syncwarp();
  if(tid<32){
    sum[tid]+=sum[tid+32];
    usum[tid]+=usum[tid+32];
  } 
  __syncwarp();
  if(tid<16){
    sum[tid]+=sum[tid+16];
    usum[tid]+=usum[tid+16];
  } 
  __syncwarp();
  if(tid<8){
    sum[tid]+=sum[tid+8];
    usum[tid]+=usum[tid+8];
  } 
  __syncwarp();
  if(tid<4){
    sum[tid]+=sum[tid+4];
    usum[tid]+=usum[tid+4];
  } 
  __syncwarp();
  if(tid<2){
    sum[tid]+=sum[tid+2];
    usum[tid]+=usum[tid+2];
  } 
  __syncwarp();
  if(tid<1){
    sum[tid]+=sum[tid+1];
    usum[tid]+=usum[tid+1];
  } 
  __syncwarp();
  if(tid==0){
    _total[bid]=sum[0];
    _unique[bid]=usum[0];
  }
}
// GPU -n Qが角にない場合のバックトラック内の再帰処理をカーネルで行う
__global__
void BitBoard_cuda_kernel_b2(unsigned int size,unsigned int mark,unsigned int* _down,unsigned int* _left,unsigned int* _right,unsigned int* _total,unsigned int* _unique,unsigned long _cond,unsigned int* board,unsigned int _row,unsigned int BOUND1,unsigned int BOUND2,unsigned int SIDEMASK,unsigned int LASTMASK,unsigned int TOPBIT,unsigned int ENDBIT)
{
  const unsigned int mask=(1<<size)-1;
  unsigned long total=0;
  unsigned int unique=0;
  int row=0;
  unsigned int bit;
  //
  //スレッド
  //
  //ブロック内のスレッドID
  unsigned const int tid=threadIdx.x;
  //グリッド内のブロックID
  unsigned const int bid=blockIdx.x;
  //全体通してのID
  unsigned const int idx=bid*blockDim.x+tid;
  //
  //シェアードメモリ
  //
  //sharedメモリを使う ブロック内スレッドで共有
  //10固定なのは現在のmask設定で
  //GPUで実行するのは最大10だから
  //THREAD_NUMはブロックあたりのスレッド数
  __shared__ unsigned int down[THREAD_NUM][10];
  down[tid][row]=_down[idx];
  __shared__ unsigned int left[THREAD_NUM][10];
  left[tid][row]=_left[idx];
  __shared__ unsigned int right[THREAD_NUM][10];
  right[tid][row]=_right[idx];
  __shared__ unsigned int bitmap[THREAD_NUM][10];
  //down,left,rightからbitmapを出す
  bitmap[tid][row]=mask&~(down[tid][row]|left[tid][row]|right[tid][row]);
  __shared__ unsigned int sum[THREAD_NUM];
  unsigned int c_aBoard[MAX];
  __shared__ unsigned int usum[THREAD_NUM];
  //余分なスレッドは動かさない 
  //GPUはSTEPS数起動するが_cond以上は空回しする
  if(idx<_cond){
    //_down,_left,_rightの情報を
    //down,left,rightに詰め直す 
    //CPU で詰め込んだ t_はSTEPS個あるが
    //ブロック内ではブロックあたりのスレッド数に限定
    //されるので idxでよい
    //
    for(int i=0;i<_row;i++){
      c_aBoard[i]=board[idx*_row+i]; //2次元配列だが1次元的に利用  
    }
    unsigned int bitmap_tid_row;
    unsigned int down_tid_row;
    unsigned int left_tid_row;
    unsigned int right_tid_row;
    while(row>=0){
      bitmap_tid_row=bitmap[tid][row];
      down_tid_row=down[tid][row];
      left_tid_row=left[tid][row];
      right_tid_row=right[tid][row];
      //
      //bitmap[tid][row]=00000000 クイーンを
      //どこにも置けないので1行上に戻る
      if(bitmap_tid_row==0){
        row--;
      }else{
        /**11 枝刈り追加**********/
        //【枝刈り】上部サイド枝刈り
        if(row+_row<BOUND1){             	
          bitmap_tid_row=bitmap[tid][row]&=~SIDEMASK;
          //【枝刈り】下部サイド枝刈り
        }else if(row+_row==BOUND2) {     	
          if((down_tid_row&SIDEMASK)==0){ 
            row--; 
            continue;
          }
          if((down_tid_row&SIDEMASK)!=SIDEMASK){ 
            bitmap_tid_row=bitmap[tid][row]&=SIDEMASK; 
          }
        }
        int save_bitmap=bitmap[tid][row];
        //クイーンを置く
        //置く場所があるかどうか
        bitmap[tid][row]^=c_aBoard[row+_row]=bit=(-bitmap_tid_row&bitmap_tid_row);       
        if((bit&mask)!=0){
          //最終行?最終行から1個前の行まで
          //無事到達したら 加算する
          if(row+1==mark){
            /***11 LASTMASK枝刈り*********************/ 
            if((save_bitmap&LASTMASK)==0){ 
              /***12 symmetryOps 省力化のためBOUND1,BOUND2,TOPBIT,ENDBITを渡す*****/ 
              //int s=BitBoard_symmetryOps(size,c_aBoard,l); 
              int s=BitBoard_symmetryOps(size,c_aBoard,BOUND1,BOUND2,TOPBIT,ENDBIT); 
              if(s!=0){
                //print(size); //print()でTOTALを++しない
                //ホストに戻す配列にTOTALを入れる
                //スレッドが1つの場合は配列は1個
                unique++; 
                total+=s;   //対称解除で得られた解数を加算
              }
              row--;
            }
          }else{
            int rowP=row+1;
            down[tid][rowP]=down_tid_row|bit;
            left[tid][rowP]=(left_tid_row|bit)<<1;
            right[tid][rowP]=(right_tid_row|bit)>>1;
            bitmap[tid][rowP]
              =mask&~(
                  down[tid][rowP]
                  |left[tid][rowP]
                  |right[tid][rowP]);
            row++;
          }
        }else{
          //置く場所がなければ1個上に
          row--;
        }
      }
    }
    //最後sum[tid]に加算する
    sum[tid]=total;
    usum[tid]=unique;
  }else{
    //_cond未満は空回しするのでtotalは加算しない
    sum[tid]=0;
    usum[tid]=0;
  } 
  //__syncthreads()でブロック内のスレッド間の同期
  //全てのスレッドが__syncthreads()に辿り着くのを待つ
  __syncthreads();if(tid<64&&tid+64<THREAD_NUM){
    sum[tid]+=sum[tid+64];
    usum[tid]+=usum[tid+64];
  }
  __syncwarp();if(tid<32){
    sum[tid]+=sum[tid+32];
    usum[tid]+=usum[tid+32];
  } 
  __syncwarp();if(tid<16){
    sum[tid]+=sum[tid+16];
    usum[tid]+=usum[tid+16];
  } 
  __syncwarp();if(tid<8){
    sum[tid]+=sum[tid+8];
    usum[tid]+=usum[tid+8];
  } 
  __syncwarp();if(tid<4){
    sum[tid]+=sum[tid+4];
    usum[tid]+=usum[tid+4];
  } 
  __syncwarp();if(tid<2){
    sum[tid]+=sum[tid+2];
    usum[tid]+=usum[tid+2];
  } 
  __syncwarp();if(tid<1){
    sum[tid]+=sum[tid+1];
    usum[tid]+=usum[tid+1];
  } 
  __syncwarp();if(tid==0){
    _total[bid]=sum[0];
    _unique[bid]=usum[0];
  }
}
// GPU -n Qが角にない
void BitBoard_backTrack2G(const unsigned int size,unsigned int row,unsigned int _left,unsigned int _down,unsigned int _right,struct local* l)
{
  //何行目からGPUで行くか。ここの設定は変更可能、設定値を多くするほどGPUで並行して動く
  /***11 size<8の時はmarkが2*********************/
  unsigned int mark=size>12?size-10:3;
  //unsigned int mark=size>11?size-9:3;
  if(size<8){ mark=2; }
  const unsigned int h_mark=row;
  unsigned long totalCond=0;
  unsigned int mask=(1<<size)-1;
  bool matched=false;
  //host
  unsigned int down[32];  down[row]=_down;
  unsigned int right[32]; right[row]=_right;
  unsigned int left[32];  left[row]=_left;
  //bitmapを配列で持つことにより
  //stackを使わないで1行前に戻れる
  unsigned int bitmap[32];
  bitmap[row]=mask&~(left[row]|down[row]|right[row]);
  unsigned int bit;

  unsigned int* hostDown;
  cudaMallocHost((void**) &hostDown,sizeof(int)*l->STEPS);
  unsigned int* hostLeft;
  cudaMallocHost((void**) &hostLeft,sizeof(int)*l->STEPS);
  unsigned int* hostRight;
  cudaMallocHost((void**) &hostRight,sizeof(int)*l->STEPS);
  unsigned int* deviceDown;
  cudaMalloc((void**) &deviceDown,sizeof(int)*l->STEPS);
  unsigned int* deviceLeft;
  cudaMalloc((void**) &deviceLeft,sizeof(int)*l->STEPS);
  unsigned int* deviceRight;
  cudaMalloc((void**) &deviceRight,sizeof(int)*l->STEPS);
  
  unsigned int* hostTotal;
  cudaMallocHost((void**) &hostTotal,sizeof(int)*l->STEPS/THREAD_NUM);
  unsigned int* hostUnique;
  cudaMallocHost((void**) &hostUnique,sizeof(int)*l->STEPS/THREAD_NUM);
  unsigned int* deviceTotal;
  cudaMalloc((void**) &deviceTotal,sizeof(int)*l->STEPS/THREAD_NUM);
  unsigned int* deviceUnique;
  cudaMalloc((void**) &deviceUnique,sizeof(int)*l->STEPS/THREAD_NUM);

  unsigned int* hostBoard;
  cudaMallocHost((void**) &hostBoard,sizeof(int)*l->STEPS*mark);
  unsigned int* deviceBoard;
  cudaMalloc((void**) &deviceBoard,sizeof(int)*l->STEPS*mark);

  //12行目までは3行目までCPU->row==mark以下で 3行目までの
  //down,left,right情報をhostDown ,hostLeft,hostRight
  //に格納
  //する->3行目以降をGPUマルチスレッドで実行し結果を取得
  //13行目以降はCPUで実行する行数が1個ずつ増えて行く
  //例えばn15だとrow=5までCPUで実行し、
  //それ以降はGPU(現在の設定だとGPUでは最大10行実行する
  //ようになっている)
  unsigned int rowP=0;
  unsigned long total=0;
  unsigned long unique=0;
  while(row>=h_mark) {
    //bitmap[row]=00000000 クイーンを
    //どこにも置けないので1行上に戻る
    //06GPU こっちのほうが優秀
    if(bitmap[row]==0){ row--; }
    else{//おける場所があれば進む
      /***11 枝刈り追加*********************/
      //【枝刈り】上部サイド枝刈り
      if(row<l->BOUND1){             	
        bitmap[row]&=~l->SIDEMASK;
        //【枝刈り】下部サイド枝刈り
      }else if(row==l->BOUND2) {     	
        if((down[row]&l->SIDEMASK)==0){ row--; }
        if((down[row]&l->SIDEMASK)!=l->SIDEMASK){ bitmap[row]&=l->SIDEMASK; }
      }
      //06SGPU
      bitmap[row]^=l->board[row]=bit=(-bitmap[row]&bitmap[row]);
      if((bit&mask)!=0){//置く場所があれば先に進む
        rowP=row+1;
        down[rowP]=down[row]|bit;
        left[rowP]=(left[row]|bit)<<1;
        right[rowP]=(right[row]|bit)>>1;
        bitmap[rowP]=mask&~(down[rowP]|left[rowP]|right[rowP]);
        row++;
        if(row==mark){
          //3行目(mark)にクイーンを1個ずつ置いていって、
          //down,left,right情報を格納、
          //その次の行へは進まない。その行で可能な場所にクイー
          //ン置き終わったらGPU並列実行
          //totalCond がthreadIdになる 各スレッドに down,left,right情報を渡す
          //row=2(13行目以降は増えていく。例えばn15だとrow=5)の情報を
          //hostDown,hostLeft,hostRightに格納する
          hostDown[totalCond]=down[row];
          hostLeft[totalCond]=left[row];
          hostRight[totalCond]=right[row];
          for(int i=0;i<mark;i++){
            hostBoard[totalCond*mark+i]=l->board[i];
          }
          //スレッド数をインクリメントする
          totalCond++;
          //最大GPU数に達してしまったら一旦ここでGPUを実行する。STEPSはGPUの同
          //時並行稼働数を制御
          //nの数が少ないうちはtotalCondがSTEPSを超えることはないがnの数が増え
          //て行くと超えるようになる。
          //ここではtotalCond==STEPSの場合だけこの中へ         
          if(totalCond==l->STEPS){
            //matched=trueの時にCOUNT追加 //GPU内でカウントしているので、GPUか
            //ら出たらmatched=trueになってる
            if(matched){
              // デバイスからホストへ転送
              cudaMemcpy(hostTotal, deviceTotal, sizeof(int)*l->STEPS/THREAD_NUM,cudaMemcpyDeviceToHost);
              cudaMemcpy(hostUnique,deviceUnique,sizeof(int)*l->STEPS/THREAD_NUM,cudaMemcpyDeviceToHost);
              for(int col=0;col<l->STEPS/THREAD_NUM;col++){
                total+=hostTotal[col];
                unique+=hostUnique[col];
              }
              matched=false;
            }
            // ホストからデバイスへ転送
            cudaMemcpy(deviceDown, hostDown,sizeof(int)*totalCond, cudaMemcpyHostToDevice);
            cudaMemcpy(deviceLeft, hostLeft,sizeof(int)*totalCond, cudaMemcpyHostToDevice);
            cudaMemcpy(deviceRight,hostRight,sizeof(int)*totalCond,cudaMemcpyHostToDevice);
            cudaMemcpy(deviceBoard,hostBoard,sizeof(int)*totalCond*mark,cudaMemcpyHostToDevice);
            // CUDA起動
            BitBoard_cuda_kernel_b2<<<l->STEPS/THREAD_NUM,THREAD_NUM >>>(size,size-mark,deviceDown,deviceLeft,deviceRight,deviceTotal,deviceUnique,totalCond,deviceBoard,row,l->BOUND1,l->BOUND2,l->SIDEMASK,l->LASTMASK,l->TOPBIT,l->ENDBIT);
            //STEPS数の数だけマルチスレッドで起動するのだが、実際に計算が行われ
            //るのはtotalCondの数だけでそれ以外は空回しになる
            //GPU内でカウントしているので、GPUから出たらmatched=trueになってる
            matched=true;
            //totalCond==STEPSルートでGPUを実行したらスレッドをまた0から開始す
            //る(これによりなんどもSTEPS数分だけGPUを起動できる)
            totalCond=0;           
          }
          //hostDown,hostLeft,hostRightに情報を格納したら1行上に上がる
          //これを繰り返すことにより row=2で可能な場所全てにクイーンを置いて
          //hostDown,hostLeft,hostRightに情報を格納する
          row--;
        }
      }else{
        //置く場所がなければ上に上がる。row==mark行に達するまではCPU側で普通に
        //nqueenをやる
        row--;
      }
    }
  }
  //matched=trueの時にCOUNT追加 //GPU内でカウントしているので、GPUから出たら
  //matched=trueになってる
  if(matched){
    // デバイスからホストへ転送
    cudaMemcpy(hostTotal, deviceTotal, sizeof(int)*l->STEPS/THREAD_NUM,cudaMemcpyDeviceToHost);
    cudaMemcpy(hostUnique,deviceUnique,sizeof(int)*l->STEPS/THREAD_NUM,cudaMemcpyDeviceToHost);
    // 集計
    for(int col=0;col<l->STEPS/THREAD_NUM;col++){
      total+=hostTotal[col];
      unique+=hostUnique[col];
    }
    matched=false;
  }
  // ホストからデバイスへ転送
  cudaMemcpy(deviceDown, hostDown,sizeof(int)*totalCond,cudaMemcpyHostToDevice);
  cudaMemcpy(deviceLeft, hostLeft,sizeof(int)*totalCond,cudaMemcpyHostToDevice);
  cudaMemcpy(deviceRight,hostRight,sizeof(int)*totalCond,cudaMemcpyHostToDevice);
  cudaMemcpy(deviceBoard,hostBoard,sizeof(int)*totalCond*mark,cudaMemcpyHostToDevice);
  //size-mark は何行GPUを実行するか totalCondはスレッド数
  //STEPS数の数だけマルチスレッドで起動するのだが、実際に計算が行われるのは
  //totalCondの数だけでそれ以外は空回しになる
  // CUDA起動
  BitBoard_cuda_kernel_b2<<<l->STEPS/THREAD_NUM,THREAD_NUM >>>(size,size-mark,deviceDown,deviceLeft,deviceRight,deviceTotal,deviceUnique,totalCond,deviceBoard,mark,l->BOUND1,l->BOUND2,l->SIDEMASK,l->LASTMASK,l->TOPBIT,l->ENDBIT);
  // デバイスからホストへ転送
  cudaMemcpy(hostTotal, deviceTotal, sizeof(int)*l->STEPS/THREAD_NUM,cudaMemcpyDeviceToHost);
  cudaMemcpy(hostUnique,deviceUnique,sizeof(int)*l->STEPS/THREAD_NUM,cudaMemcpyDeviceToHost);
  // 集計
  for(int col=0;col<l->STEPS/THREAD_NUM;col++){
    total+=hostTotal[col];
    unique+=hostUnique[col];
  }
  TOTAL+=total;
  UNIQUE+=unique;
  //
  cudaFree(deviceDown);
  cudaFree(deviceLeft);
  cudaFree(deviceRight);
  cudaFree(deviceTotal);
  cudaFree(deviceUnique);
  cudaFree(deviceBoard);
  cudaFreeHost(hostDown);
  cudaFreeHost(hostLeft);
  cudaFreeHost(hostRight);
  cudaFreeHost(hostTotal);
  cudaFreeHost(hostUnique);
  cudaFreeHost(hostBoard);
}
// GPU -n Qが角にある
void BitBoard_backTrack1G(const unsigned int size,unsigned int row,unsigned int _left,unsigned int _down,unsigned int _right,struct local* l)
{
  //何行目からGPUで行くか。ここの設定は変更可能、設定値を多くするほどGPUで並行して動く
  /***08 クイーンを2行目まで固定で置くためmarkが3以上必要*********************/
  const unsigned int mark=size>12?size-10:3;
  //mark 行までCPU mark行以降はGPU
  const unsigned int h_mark=row;
  const unsigned int mask=(1<<size)-1;
  unsigned long totalCond=0;
  bool matched=false;
  //host
  unsigned int down[32];  down[row]=_down;
  unsigned int right[32]; right[row]=_right;
  unsigned int left[32];  left[row]=_left;
  //bitmapを配列で持つことにより
  //stackを使わないで1行前に戻れる
  unsigned int bitmap[32];
  bitmap[row]=mask&~(left[row]|down[row]|right[row]);
  unsigned int bit;

  unsigned int* hostDown;
  cudaMallocHost((void**) &hostDown,sizeof(int)*l->STEPS);
  unsigned int* hostLeft;
  cudaMallocHost((void**) &hostLeft,sizeof(int)*l->STEPS);
  unsigned int* hostRight;
  cudaMallocHost((void**) &hostRight,sizeof(int)*l->STEPS);
  unsigned int* deviceDown;
  cudaMalloc((void**) &deviceDown,sizeof(int)*l->STEPS);
  unsigned int* deviceLeft;
  cudaMalloc((void**) &deviceLeft,sizeof(int)*l->STEPS);
  unsigned int* deviceRight;
  cudaMalloc((void**) &deviceRight,sizeof(int)*l->STEPS);

  unsigned int* hostTotal;
  cudaMallocHost((void**) &hostTotal,sizeof(int)*l->STEPS/THREAD_NUM);
  unsigned int* hostUnique;
  cudaMallocHost((void**) &hostUnique,sizeof(int)*l->STEPS/THREAD_NUM);
  unsigned int* deviceTotal;
  cudaMalloc((void**) &deviceTotal,sizeof(int)*l->STEPS/THREAD_NUM);
  unsigned int* deviceUnique;
  cudaMalloc((void**) &deviceUnique,sizeof(int)*l->STEPS/THREAD_NUM);

  //12行目までは3行目までCPU->row==mark以下で 3行目までの
  //down,left,right情報を hostDown,hostLeft,hostRight
  //に格納
  //する->3行目以降をGPUマルチスレッドで実行し結果を取得
  //13行目以降はCPUで実行する行数が1個ずつ増えて行く
  //例えばn15だとrow=5までCPUで実行し、
  //それ以降はGPU(現在の設定だとGPUでは最大10行実行する
  //ようになっている)
  //while(row>=0) {
  int rowP=0;
  unsigned long total=0;
  unsigned long unique=0;
  while(row>=h_mark) {
    //bitmap[row]=00000000 クイーンを
    //どこにも置けないので1行上に戻る
    //06GPU こっちのほうが優秀
    if(bitmap[row]==0){ row--; }
    else{//おける場所があれば進む
      if(row<l->BOUND1) { /***11 枝刈り*********************/
        bitmap[row]&=~2; // bm|=2; bm^=2; (bm&=~2と同等)
      }
      bitmap[row]^=bit=(-bitmap[row]&bitmap[row]);
      if((bit&mask)!=0){//置く場所があれば先に進む
        rowP=row+1;
        down[rowP]=down[row]|bit;
        left[rowP]=(left[row]|bit)<<1;
        right[rowP]=(right[row]|bit)>>1;
        bitmap[rowP]=mask&~(down[rowP]|left[rowP]|right[rowP]);
        row++;
        if(row==mark){
          //3行目(mark)にクイーンを1個ずつ置いていって、
          //down,left,right情報を格納、
          //その次の行へは進まない。その行で可能な場所にクイー
          //ン置き終わったらGPU並列実行
          //totalCond がthreadIdになる 各スレッドに down,left,right情報を渡す
          //row=2(13行目以降は増えていく。例えばn15だとrow=5)の情報を
          //hostDown,hostLeft,hostRightに格納する         
          hostDown[totalCond]=down[row];
          hostLeft[totalCond]=left[row];
          hostRight[totalCond]=right[row];
          //スレッド数をインクリメントする
          totalCond++;
          //最大GPU数に達してしまったら一旦ここでGPUを実行する。STEPSはGPUの同
          //時並行稼働数を制御
          //nの数が少ないうちはtotalCondがSTEPSを超えることはないがnの数が増え
          //て行くと超えるようになる。
          //ここではtotalCond==STEPSの場合だけこの中へ         
          if(totalCond==l->STEPS){
            //matched=trueの時にCOUNT追加 //GPU内でカウントしているので、GPUか
            //ら出たらmatched=trueになってる
            if(matched){
              // デバイスからホストへ転送
              cudaMemcpy(hostTotal, deviceTotal, sizeof(int)*l->STEPS/THREAD_NUM,cudaMemcpyDeviceToHost);
              cudaMemcpy(hostUnique,deviceUnique,sizeof(int)*l->STEPS/THREAD_NUM,cudaMemcpyDeviceToHost);
              // 集計
              for(int col=0;col<l->STEPS/THREAD_NUM;col++){
                total+=hostTotal[col];
                unique+=hostUnique[col];
              }
              matched=false;
            }
            // ホストからデバイスへ転送
            cudaMemcpy(deviceDown, hostDown, sizeof(int)*totalCond,cudaMemcpyHostToDevice);
            cudaMemcpy(deviceLeft, hostLeft, sizeof(int)*totalCond,cudaMemcpyHostToDevice);
            cudaMemcpy(deviceRight,hostRight,sizeof(int)*totalCond,cudaMemcpyHostToDevice);
            // CUDA起動
            BitBoard_cuda_kernel_b1<<<l->STEPS/THREAD_NUM,THREAD_NUM >>>(size,size-mark,deviceDown,deviceLeft,deviceRight,deviceTotal,deviceUnique,totalCond,row,l->BOUND1);
            //STEPS数の数だけマルチスレッドで起動するのだが、実際に計算が行われ
            //るのはtotalCondの数だけでそれ以外は空回しになる
            //GPU内でカウントしているので、GPUから出たらmatched=trueになってる
            matched=true;
            //totalCond==STEPSルートでGPUを実行したらスレッドをまた0から開始す
            //る(これによりなんどもSTEPS数分だけGPUを起動できる)
            totalCond=0;           
          }
          //hostDown,hostLeft,hostRightに情報を格納したら1行上に上がる
          //これを繰り返すことにより row=2で可能な場所全てにクイーンを置いて
          //hostDown,hostLeft,hostRightに情報を格納する
          row--;
        }
      }else{
        //置く場所がなければ上に上がる。row==mark行に達するまではCPU側で普通に
        //nqueenをやる
        row--;
      }
    }
  }
  //if(totalCond==l->STEPS){で処理されなかった残りがここで実行される
  //matched=trueの時にCOUNT追加 //GPU内でカウントしているので、GPUから出たら
  //matched=trueになってる
  //
  if(matched){
    // デバイスからホストへ転送
    cudaMemcpy(hostTotal, deviceTotal, sizeof(int)*l->STEPS/THREAD_NUM,cudaMemcpyDeviceToHost);
    cudaMemcpy(hostUnique,deviceUnique,sizeof(int)*l->STEPS/THREAD_NUM,cudaMemcpyDeviceToHost);
    // 集計
    for(int col=0;col<l->STEPS/THREAD_NUM;col++){
      total+=hostTotal[col];
      unique+=hostUnique[col];
    }
    matched=false;
  }
  // ホストからデバイスへ転送
  cudaMemcpy(deviceDown, hostDown, sizeof(int)*totalCond,cudaMemcpyHostToDevice);
  cudaMemcpy(deviceLeft, hostLeft, sizeof(int)*totalCond,cudaMemcpyHostToDevice);
  cudaMemcpy(deviceRight,hostRight,sizeof(int)*totalCond,cudaMemcpyHostToDevice);
  // CUDA起動
  BitBoard_cuda_kernel_b1<<<l->STEPS/THREAD_NUM,THREAD_NUM >>>(size,size-mark,deviceDown,deviceLeft,deviceRight,deviceTotal,deviceUnique,totalCond,mark,l->BOUND1);
  // デバイスからホストへ転送
  cudaMemcpy(hostTotal, deviceTotal, sizeof(int)*l->STEPS/THREAD_NUM,cudaMemcpyDeviceToHost);
  cudaMemcpy(hostUnique,deviceUnique,sizeof(int)*l->STEPS/THREAD_NUM,cudaMemcpyDeviceToHost);
  // 集計
  for(int col=0;col<l->STEPS/THREAD_NUM;col++){
    total+=hostTotal[col];
    unique+=hostUnique[col];
  }
  TOTAL+=total;
  UNIQUE+=unique;
  //開放
  cudaFree(deviceDown);
  cudaFree(deviceLeft);
  cudaFree(deviceRight);
  cudaFree(deviceTotal);
  cudaFree(deviceUnique);
  cudaFreeHost(hostDown);
  cudaFreeHost(hostLeft);
  cudaFreeHost(hostRight);
  cudaFreeHost(hostTotal);
  cudaFreeHost(hostUnique);
}
// GPU -n ビットボードの実行 角にQがある・ないの分岐を行う
void BitBoard_build(const unsigned int size,int STEPS)
{
  if(size<=0||size>32){return;}
  /**
    int型は unsigned とする
    total: グローバル変数TOTALへのアクセスを極小化する
    */
  struct local l; //GPU で扱う構造体
  l.STEPS=STEPS;
  unsigned int bit=1;
  l.board[0]=1;
  unsigned int left=bit<<1,down=bit,right=bit>>1;
  /**
    2行目は右から3列目から左端から2列目まで
  */
  for(l.BOUND1=2;l.BOUND1<size-1;l.BOUND1++){
    l.board[1]=bit=(1<<l.BOUND1);
    BitBoard_backTrack1G(size,2,(left|bit)<<1,(down|bit),(right|bit)>>1,&l);
  }
  l.TOPBIT=1<<(size-1);
  l.SIDEMASK=l.LASTMASK=(l.TOPBIT|1);
  l.ENDBIT=(l.TOPBIT>>1);
  /**
    1行目右から2列目から
    偶数個は1/2 n=8 なら 1,2,3 奇数個は1/2+1 n=9 なら 1,2,3,4
  */
  for(l.BOUND1=1,l.BOUND2=size-1-1;l.BOUND1<l.BOUND2;l.BOUND1++,l.BOUND2--){
    l.board[0]=bit=(1<<l.BOUND1);
    BitBoard_backTrack2G(size,1,bit<<1,bit,bit>>1,&l);
    l.LASTMASK|=l.LASTMASK>>1|l.LASTMASK<<1;
    l.ENDBIT>>=1;
  }
}
// CUDA 初期化
bool InitCUDA()
{
  int count;
  cudaGetDeviceCount(&count);
  if(count==0){fprintf(stderr,"There is no device.\n");return false;}
  unsigned int i;
  for(i=0;i<count;++i){
    struct cudaDeviceProp prop;
    if(cudaGetDeviceProperties(&prop,i)==cudaSuccess){if(prop.major>=1){break;} }
  }
  if(i==count){fprintf(stderr,"There is no device supporting CUDA 1.x.\n");return false;}
  cudaSetDevice(i);
  return true;
}
//メイン
int main(int argc,char** argv)
{
  bool cpu=false,cpur=false,gpu=false,gpuBitBoard=false;
  unsigned int argstart=2;
  if(argc>=2&&argv[1][0]=='-'){
    if(argv[1][1]=='c'||argv[1][1]=='C'){cpu=true;}
    else if(argv[1][1]=='r'||argv[1][1]=='R'){cpur=true;}
    else if(argv[1][1]=='c'||argv[1][1]=='C'){cpu=true;}
    else if(argv[1][1]=='g'||argv[1][1]=='G'){gpu=true;}
    else if(argv[1][1]=='n'||argv[1][1]=='N'){gpuBitBoard=true;}
    else{ gpuBitBoard=true; } //デフォルトをgpuとする
    argstart=2;
  }
  if(argc<argstart){
    printf("Usage: %s [-c|-g|-r|-s] n STEPS\n",argv[0]);
    printf("  -r: CPU 再帰\n");
    printf("  -c: CPU 非再帰\n");
    printf("  -g: GPU 再帰\n");
    printf("  -n: GPU ビットボード\n");
  }
  if(cpur){ printf("\n\n対称解除法 再帰 \n"); }
  else if(cpu){ printf("\n\n対称解除法 非再帰 \n"); }
  else if(gpu){ printf("\n\n対称解除法 GPU\n"); }
  else if(gpuBitBoard){ printf("\n\n対称解除法 GPUビットボード \n"); }
  if(cpu||cpur)
  {
    unsigned int min=4; 
    unsigned int targetN=17;
    struct timeval t0;
    struct timeval t1;
    printf("%s\n"," N:        Total      Unique      dd:hh:mm:ss.ms");
    for(unsigned int size=min;size<=targetN;size++){
      local l;
      gettimeofday(&t0,NULL);//計測開始
      if(cpur){ //再帰
        symmetry_R(size,&l);
      }
      if(cpu){ //非再帰
        symmetry_NR(size,&l);
      }
      //
      gettimeofday(&t1,NULL);//計測終了
      unsigned int ss;
      unsigned int ms;
      unsigned int dd;
      if(t1.tv_usec<t0.tv_usec) {
        dd=(t1.tv_sec-t0.tv_sec-1)/86400;
        ss=(t1.tv_sec-t0.tv_sec-1)%86400;
        ms=(1000000+t1.tv_usec-t0.tv_usec+500)/10000;
      }else {
        dd=(t1.tv_sec-t0.tv_sec)/86400;
        ss=(t1.tv_sec-t0.tv_sec)%86400;
        ms=(t1.tv_usec-t0.tv_usec+500)/10000;
      }//end if
      unsigned int hh=ss/3600;
      unsigned int mm=(ss-hh*3600)/60;
      ss%=60;
      printf("%2d:%13ld%12ld%8.2d:%02d:%02d:%02d.%02d\n",size,TOTAL,UNIQUE,dd,hh,mm,ss,ms);
    } //end for
  }//end if
  if(gpu||gpuBitBoard)
  {
    int STEPS=24576;
    if(!InitCUDA()){return 0;}
    unsigned int min=4;
    unsigned int targetN=27;
    struct timeval t0;
    struct timeval t1;
    printf("%s\n"," N:        Total      Unique      dd:hh:mm:ss.ms");
    for(unsigned int size=min;size<=targetN;size++){
      gettimeofday(&t0,NULL);
      if(gpu){
        TOTAL=UNIQUE=0;
        local l[MAX];
        GPU_symmetry_R(size,&l[0]);
        TOTAL=l->TOTAL;
        UNIQUE=l->UNIQUE;
      }else if(gpuBitBoard){
        TOTAL=UNIQUE=0;
        BitBoard_build(size,STEPS);
      }
      gettimeofday(&t1,NULL);
      unsigned int ss;
      unsigned int ms;
      unsigned int dd;
      if (t1.tv_usec<t0.tv_usec) {
        dd=(int)(t1.tv_sec-t0.tv_sec-1)/86400;
        ss=(t1.tv_sec-t0.tv_sec-1)%86400;
        ms=(1000000+t1.tv_usec-t0.tv_usec+500)/10000;
      } else {
        dd=(int)(t1.tv_sec-t0.tv_sec)/86400;
        ss=(t1.tv_sec-t0.tv_sec)%86400;
        ms=(t1.tv_usec-t0.tv_usec+500)/10000;
      }//end if
      unsigned int hh=ss/3600;
      unsigned int mm=(ss-hh*3600)/60;
      ss%=60;
      printf("%2d:%13ld%12ld%8.2d:%02d:%02d:%02d.%02d\n",size,TOTAL,UNIQUE,dd,hh,mm,ss,ms);
    }
  }
  return 0;
}

CUDA 実行結果

 GPU で並列処理で実行(ビットボード)
 $ nvcc -O3 -arch=sm_61 03CUDA_Symmetry_BitBoard.cu && ./a.out -n

対称解除法 GPUビットボード
 N:            Total           Unique      dd:hh:mm:ss.ms
 4:                2                1      00:00:00:00.13
 5:               10                2      00:00:00:00.00
 6:                4                1      00:00:00:00.00
 7:               40                6      00:00:00:00.00
 8:               92               12      00:00:00:00.00
 9:              352               46      00:00:00:00.00
10:              724               92      00:00:00:00.01
11:             2680              341      00:00:00:00.01
12:            14200             1787      00:00:00:00.01
13:            73712             9233      00:00:00:00.03
14:           365596            45752      00:00:00:00.03
15:          2279184           285053      00:00:00:00.03
16:         14772512          1846955      00:00:00:00.05
17:         95815104         11977939      00:00:00:00.24
18:        666090624         83263591      00:00:00:01.75
19:       4968057848        621012754      00:00:00:14.52
20:      39029188884       4878666808      00:00:02:08.10
21:     314666222712      39333324973      00:00:19:56.19
22:    2691008701644     336376244042      00:03:08:14.53
23:   24233937684440    3029242658210      01:06:56:07.12
24:  227514171973736   28439272956934      13:05:38:39.13

次回は、キャリーチェーンに行きます!

参考リンク

以下の詳細説明を参考にしてください。
【参考リンク】Nクイーン問題 過去記事一覧
【Github】エイト・クイーンのソース置き場 BashもJavaもPythonも!

Nクイーン問題(50)第七章 マルチプロセス Python編
https://suzukiiichiro.github.io/posts/2023-06-21-04-n-queens-suzuki/
Nクイーン問題(49)第七章 マルチスレッド Python編
https://suzukiiichiro.github.io/posts/2023-06-21-03-n-queens-suzuki/
Nクイーン問題(48)第七章 シングルスレッド Python編
https://suzukiiichiro.github.io/posts/2023-06-21-02-n-queens-suzuki/
Nクイーン問題(47)第七章 クラス Python編
https://suzukiiichiro.github.io/posts/2023-06-21-01-n-queens-suzuki/
Nクイーン問題(46)第七章 ステップNの実装 Python編
https://suzukiiichiro.github.io/posts/2023-06-16-02-n-queens-suzuki/
Nクイーン問題(45)第七章 キャリーチェーン Python編
https://suzukiiichiro.github.io/posts/2023-06-16-01-n-queens-suzuki/
Nクイーン問題(44)第七章 対象解除法 Python編
https://suzukiiichiro.github.io/posts/2023-06-14-02-n-queens-suzuki/
Nクイーン問題(43)第七章 ミラー Python編
https://suzukiiichiro.github.io/posts/2023-06-14-01-n-queens-suzuki/
Nクイーン問題(42)第七章 ビットマップ Python編
https://suzukiiichiro.github.io/posts/2023-06-13-05-n-queens-suzuki/
Nクイーン問題(41)第七章 配置フラグ Python編
https://suzukiiichiro.github.io/posts/2023-06-13-04-n-queens-suzuki/
Nクイーン問題(40)第七章 バックトラック Python編
https://suzukiiichiro.github.io/posts/2023-06-13-03-n-queens-suzuki/
Nクイーン問題(39)第七章 バックトラック準備編 Python編
https://suzukiiichiro.github.io/posts/2023-06-13-02-n-queens-suzuki/
Nクイーン問題(38)第七章 ブルートフォース Python編
https://suzukiiichiro.github.io/posts/2023-06-13-01-n-queens-suzuki/
Nクイーン問題(37)第六章 C言語移植 その17 pthread並列処理完成
https://suzukiiichiro.github.io/posts/2023-05-30-17-n-queens-suzuki/
Nクイーン問題(36)第六章 C言語移植 その16 pthreadの実装
https://suzukiiichiro.github.io/posts/2023-05-30-16-n-queens-suzuki/
Nクイーン問題(35)第六章 C言語移植 その15 pthread実装直前版完成
https://suzukiiichiro.github.io/posts/2023-05-30-15-n-queens-suzuki/
Nクイーン問題(34)第六章 C言語移植 その14
https://suzukiiichiro.github.io/posts/2023-05-30-14-n-queens-suzuki/
Nクイーン問題(33)第六章 C言語移植 その13
https://suzukiiichiro.github.io/posts/2023-05-30-13-n-queens-suzuki/
Nクイーン問題(32)第六章 C言語移植 その12
https://suzukiiichiro.github.io/posts/2023-05-30-12-n-queens-suzuki/
Nクイーン問題(31)第六章 C言語移植 その11
https://suzukiiichiro.github.io/posts/2023-05-30-11-n-queens-suzuki/
Nクイーン問題(30)第六章 C言語移植 その10
https://suzukiiichiro.github.io/posts/2023-05-30-10-n-queens-suzuki/
Nクイーン問題(29)第六章 C言語移植 その9
https://suzukiiichiro.github.io/posts/2023-05-30-09-n-queens-suzuki/
Nクイーン問題(28)第六章 C言語移植 その8
https://suzukiiichiro.github.io/posts/2023-05-30-08-n-queens-suzuki/
Nクイーン問題(27)第六章 C言語移植 その7
https://suzukiiichiro.github.io/posts/2023-05-30-07-n-queens-suzuki/
Nクイーン問題(26)第六章 C言語移植 その6
https://suzukiiichiro.github.io/posts/2023-05-30-06-n-queens-suzuki/
Nクイーン問題(25)第六章 C言語移植 その5
https://suzukiiichiro.github.io/posts/2023-05-30-05-n-queens-suzuki/
Nクイーン問題(24)第六章 C言語移植 その4
https://suzukiiichiro.github.io/posts/2023-05-30-04-n-queens-suzuki/
Nクイーン問題(23)第六章 C言語移植 その3
https://suzukiiichiro.github.io/posts/2023-05-30-03-n-queens-suzuki/
Nクイーン問題(22)第六章 C言語移植 その2
https://suzukiiichiro.github.io/posts/2023-05-30-02-n-queens-suzuki/
Nクイーン問題(21)第六章 C言語移植 その1
N-Queens問://suzukiiichiro.github.io/posts/2023-05-30-01-n-queens-suzuki/
Nクイーン問題(20)第五章 並列処理
https://suzukiiichiro.github.io/posts/2023-05-23-02-n-queens-suzuki/
Nクイーン問題(19)第五章 キャリーチェーン
https://suzukiiichiro.github.io/posts/2023-05-23-01-n-queens-suzuki/
Nクイーン問題(18)第四章 エイト・クイーンノスタルジー
https://suzukiiichiro.github.io/posts/2023-04-25-01-n-queens-suzuki/
Nクイーン問題(17)第四章 偉人のソースを読む「N24を発見 Jeff Somers」
https://suzukiiichiro.github.io/posts/2023-04-21-01-n-queens-suzuki/
Nクイーン問題(16)第三章 対象解除法 ソース解説
https://suzukiiichiro.github.io/posts/2023-04-18-01-n-queens-suzuki/
Nクイーン問題(15)第三章 対象解除法 ロジック解説
https://suzukiiichiro.github.io/posts/2023-04-13-02-nqueens-suzuki/
Nクイーン問題(14)第三章 ミラー
https://suzukiiichiro.github.io/posts/2023-04-13-01-nqueens-suzuki/
Nクイーン問題(13)第三章 ビットマップ
https://suzukiiichiro.github.io/posts/2023-04-05-01-nqueens-suzuki/
Nクイーン問題(12)第二章 まとめ
https://suzukiiichiro.github.io/posts/2023-03-17-02-n-queens-suzuki/
Nクイーン問題(11)第二章 配置フラグの再帰・非再帰
https://suzukiiichiro.github.io/posts/2023-03-17-01-n-queens-suzuki/
Nクイーン問題(10)第二章 バックトラックの再帰・非再帰
https://suzukiiichiro.github.io/posts/2023-03-16-01-n-queens-suzuki/
Nクイーン問題(9)第二章 ブルートフォースの再帰・非再帰
https://suzukiiichiro.github.io/posts/2023-03-14-01-n-queens-suzuki/
Nクイーン問題(8)第一章 まとめ
https://suzukiiichiro.github.io/posts/2023-03-09-01-n-queens-suzuki/
Nクイーン問題(7)第一章 ブルートフォース再び
https://suzukiiichiro.github.io/posts/2023-03-08-01-n-queens-suzuki/
Nクイーン問題(6)第一章 配置フラグ
https://suzukiiichiro.github.io/posts/2023-03-07-01-n-queens-suzuki/
Nクイーン問題(5)第一章 進捗表示テーブルの作成
https://suzukiiichiro.github.io/posts/2023-03-06-01-n-queens-suzuki/
Nクイーン問題(4)第一章 バックトラック
https://suzukiiichiro.github.io/posts/2023-02-21-01-n-queens-suzuki/
Nクイーン問題(3)第一章 バックトラック準備編
https://suzukiiichiro.github.io/posts/2023-02-14-03-n-queens-suzuki/
Nクイーン問題(2)第一章 ブルートフォース
https://suzukiiichiro.github.io/posts/2023-02-14-02-n-queens-suzuki/
Nクイーン問題(1)第一章 エイトクイーンについて
https://suzukiiichiro.github.io/posts/2023-02-14-01-n-queens-suzuki/

書籍の紹介

Nクイーン問題(64)第七章 並列処理 キャリーチェーン NVIDIA CUDA編

Nクイーン問題(64)第七章 並列処理 キャリーチェーン NVIDIA CUDA編

Nクイーン問題(62)第七章 並列処理 対称解除法 ノードレイヤー NVIDIA CUDA編

Nクイーン問題(62)第七章 並列処理 対称解除法 ノードレイヤー NVIDIA CUDA編