ソースコード
今回の連載 python/codonのソースコードディレクトリはこちら
https://github.com/suzukiiichiro/N-Queens/tree/master/13Bit_codon
Nクイーン問題 過去記事アーカイブ
【過去記事アーカイブ】Nクイーン問題 過去記事一覧
https://suzukiiichiro.github.io/search/?keyword=Nクイーン問題
【Github】エイト・クイーンのソース置き場 BashもJavaもPythonも!
https://github.com/suzukiiichiro/N-Queens
Python/codon Nクイーン コンステレーション版 インテグレート
, #_
~\_ ####_ N-Queens
~~ \_#####\ https://suzukiiichiro.github.io/
~~ \###| N-Queens for github
~~ \#/ ___ https://github.com/suzukiiichiro/N-Queens
~~ V~' '->
~~~ /
~~._. _/
_/ _/
_/m/'
結論:codon for python 17Py_ は GPU/CUDA 10Bit_CUDA/01CUDA_Bit_Symmetry.cu と同等の速度で動作します。
$ nvcc -O3 -arch=sm_61 -m64 -ptx -prec-div=false 04CUDA_Symmetry_BitBoard.cu && POCL_DEBUG=all ./a.out -n
対称解除法 GPUビットボード
20: 39029188884 4878666808 000:00:02:02.52
21: 314666222712 39333324973 000:00:18:46.52
22: 2691008701644 336376244042 000:03:00:22.54
23: 24233937684440 3029242658210 001:06:03:49.29
amazon AWS m4.16xlarge x 1
$ codon build -release 15Py_constellations_optimize_codon.py && ./15Py_constellations_optimize_codon
20: 39029188884 0 0:02:52.430
21: 314666222712 0 0:24:25.554
22: 2691008701644 0 3:29:33.971
23: 24233937684440 0 1 day, 8:12:58.977
Python でも動作(15py_ 以降の並列処理を除く)
$ python <filename.py>
codon 実行方法
# ビルドせず実行
$ codon run <filename.py>
# C/C++ ネイティブに変換して高速実行
$ codon build -release <filename.py> && ./<filename>
参考リンク
- Nクイーン問題 過去記事一覧
https://suzukiiichiro.github.io/search/?keyword=Nクイーン問題 - エイト・クイーンのプログラムアーカイブ(Bash/Lua/C/Java/Python/CUDA など)
https://github.com/suzukiiichiro/N-Queens
N-Queens 高速ソルバ(NQueens17)
- ビットボード + “星座(Constellation)”分割探索
- 回転・鏡像対称の正規化(Jasmin) と 重複補正(symmetry)
- 事前配置(preset_queens) で探索を分割し、部分盤面ごとに DFS
- メモ化・キャッシュ(サブコンステ/状態/Zobrist/星座 I/O)
目的
大きめの N でも高速に全解数を数える。探索の「入口」を 星座 で細分化し、各星座を最適な分岐(旧 SQ* 系)で解く。
この実装のキモ
-
64bit マスク定義:
MASK64 = (1<<64) - 1 -
Zobrist の永続キャッシュ:
self.zobrist_tables[N]を使い回し
→zobrist_hash()は必ずself.init_zobrist(N)後に参照
例:self.init_zobrist(N); tbl = self.zobrist_tables[N] -
N ビット正規化の徹底(上位ビット落とし/負数対策)
ld &= (1<<N)-1; rd &= (1<<N)-1; col &= (1<<N)-1; ... -
盤面の正規化:
jasmin()で(i, j, k, l)を回転/鏡像して代表形へ -
対称重複補正:
symmetry(ijkl, N)が 2/4/8 を返し、解数を補正 -
探索の枝刈り
state_hash(軽量 O(1))で visited を管理(衝突許容)- Zobrist(低衝突)も切替可(N≤17 では O(1) 版で十分)
-
DFS の一本化(
dfs()):
旧 SQ* 分岐を function id + メタ情報(func_meta) に集約。
分岐・先読み・ブロックを同一配置ループで処理して高速化。
パフォーマンスの要点
- ループ内辞書アクセス削減(ローカル束縛例:
ld_tbl, rd_tbl = tbl['ld'], tbl['rd']) - 立っているビットのみの走査
- SoA 配列化:
exec_solutions()前処理で配列に詰め、並列セクションを軽量化
注意 / 設計上のポイント
- Zobrist 初期化は必ず
init_zobrist()経由。zobrist_hash()内で新規 dict を作らない。 - N ビット正規化忘れはハッシュ不一致・誤カウントの原因。
set_pre_queens_*の visited はキー衝突に注意(N≤17 では実害少)。- I/O キャッシュ(
.txt/.bin)読込時はvalidate_*でフォーマット検証。
代表的な引用
- 64bit マスク:
MASK64 = (1<<64) - 1 - Zobrist テーブル使用:
self.init_zobrist(N); tbl = self.zobrist_tables[N] - N ビット正規化:
mask = (1<<N)-1; ld &= mask; rd &= mask; col &= mask; LD &= mask; RD &= mask - 回転/鏡像の正規化(Jasmin):
jasmin(ijkl, N) - 対称重複補正:
symmetry(ijkl, N) -> {2,4,8} - 一本化 DFS:
dfs(functionid, ld, rd, col, row, free, ...)
17Py_constellations_integrate_codon.py(レビュー&注釈)
amazon AWS m4.16xlarge x 1
$ codon build -release 15Py_constellations_optimize_codon.py && ./15Py_constellations_optimize_codon
N: Total Unique hh:mm:ss.ms
5: 10 0 0:00:00.000
6: 4 0 0:00:00.079
7: 40 0 0:00:00.001
8: 92 0 0:00:00.001
9: 352 0 0:00:00.001
10: 724 0 0:00:00.002
11: 2680 0 0:00:00.102
12: 14200 0 0:00:00.002
13: 73712 0 0:00:00.005
14: 365596 0 0:00:00.011
15: 2279184 0 0:00:00.035
16: 14772512 0 0:00:00.078
17: 95815104 0 0:00:00.436
18: 666090624 0 0:00:02.961
19: 4968057848 0 0:00:22.049
20: 39029188884 0 0:02:52.430
21: 314666222712 0 0:24:25.554
22: 2691008701644 0 3:29:33.971
23: 24233937684440 0 1 day, 8:12:58.977
ワークスペース実行
workspace#suzuki$ bash MAIN.SH 15Py_constellations_optimize_codon.py
N: Total Unique hh:mm:ss.ms
17: 95815104 0 0:00:02.987
18: 666090624 0 0:00:21.549
19: 4968057848 0 0:02:43.514
workspace#suzuki$ bash MAIN.SH 17Py_constellations_integrate_codon_20251017_suzuki.py
N: Total Unique hh:mm:ss.ms
17: 95815104 0 0:00:04.037 ok
18: 666090624 0 0:00:29.301 ok
19: 4968057848 0 0:03:41.853 ok
ソースコード
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Python/codon Nクイーン コンステレーション版 インテグレート
, #_
~\_ ####_ N-Queens
~~ \_#####\ https://suzukiiichiro.github.io/
~~ \###| N-Queens for github
~~ \#/ ___ https://github.com/suzukiiichiro/N-Queens
~~ V~' '->
~~~ /
~~._. _/
_/ _/
_/m/'
結論から言えば codon for python 17Py_ は GPU/CUDA 10Bit_CUDA/01CUDA_Bit_Symmetry.cu と同等の速度で動作します。
$ nvcc -O3 -arch=sm_61 -m64 -ptx -prec-div=false 04CUDA_Symmetry_BitBoard.cu && POCL_DEBUG=all ./a.out -n ;
対称解除法 GPUビットボード
20: 39029188884 4878666808 000:00:02:02.52
21: 314666222712 39333324973 000:00:18:46.52
22: 2691008701644 336376244042 000:03:00:22.54
23: 24233937684440 3029242658210 001:06:03:49.29
amazon AWS m4.16xlarge x 1
$ codon build -release 15Py_constellations_optimize_codon.py && ./15Py_constellations_optimize_codon
20: 39029188884 0 0:02:52.430
21: 314666222712 0 0:24:25.554
22: 2691008701644 0 3:29:33.971
23: 24233937684440 0 1 day, 8:12:58.977
python 15py_ 以降の並列処理を除けば python でも動作します
$ python <filename.py>
codon for python ビルドしない実行方法
$ codon run <filename.py>
codon build for python ビルドすればC/C++ネイティブに変換し高速に実行します
$ codon build -release < filename.py> && ./<filename>
詳細はこちら。
【参考リンク】Nクイーン問題 過去記事一覧はこちらから
https://suzukiiichiro.github.io/search/?keyword=Nクイーン問題
エイト・クイーンのプログラムアーカイブ
Bash、Lua、C、Java、Python、CUDAまで!
https://github.com/suzukiiichiro/N-Queens
"""
"""
N-Queens 高速ソルバ(NQueens17)
- ビットボード + “星座(Constellation)”分割探索
- 回転・鏡像対称の正規化(Jasmin)と重複補正(symmetry)
- 事前配置(preset_queens)で探索を分割し、部分盤面ごとにDFS
- メモ化・キャッシュ(sub-constellation/state/Zobrist/星座I/O)
■ 目的
大きめの N でも高速に全解数を数える。探索の“入口”を「星座」で細分化し、
各星座を最適な分岐(SQ* 系)で解く。
■ この実装のキモ
- 64bit マスク定義: MASK64 = (1<<64) - 1
- Zobrist の永続キャッシュ: self.zobrist_tables[N] を使い回し
-> zobrist_hash() は必ず self.init_zobrist(N) を呼んでから参照
例: self.init_zobrist(N); tbl = self.zobrist_tables[N]
- N ビット正規化の徹底(上位ビット落とし/負数対策)
ld &= (1<<N)-1; rd &= (1<<N)-1; col &= (1<<N)-1; ...
- 盤面の正規化: jasmin() で (i,j,k,l) を回転/鏡像して代表形へ
- 対称重複補正: symmetry(ijkl,N) が 2/4/8 を返し、解数を補正
- 探索の枝刈り:
* state_hash (軽量O(1)) で visited を管理(衝突許容)
* Zobrist(低衝突)も使用可(N<=17 では O(1)版で十分)
- DFS の一本化(dfs()):
SQ* の分岐を function id + メタ情報(func_meta)に集約し、分岐・先読み・ブロックを
同じ配置ループで処理して高速化。
■ パフォーマンスの要点
- ループ内辞書アクセスの削減(ローカル束縛):
ld_tbl, rd_tbl = tbl['ld'], tbl['rd'] など
- “立っているビットのみ”の走査(必要なら差し替え可)
- SoA 配列化 → exec_solutions() の前処理で配列へ詰め、並列セクションを軽量化
■ 注意/設計上のポイント
- Zobrist 初期化は必ず init_zobrist() 経由で。zobrist_hash() 内で new dict を作らない。
- N ビット正規化を忘れるとハッシュ不一致・誤カウントの原因に。
- set_pre_queens_* の visited はキー衝突に留意(N<=17 では実害少)。
- I/O キャッシュ(.txt / .bin)読込時は validate_* でフォーマット検証。
■ 代表的な引用
- “64bit マスク”: MASK64 = (1<<64) - 1
- “Zobrist テーブル使用”:
self.init_zobrist(N); tbl = self.zobrist_tables[N]
- “N ビット正規化”:
mask = (1<<N)-1; ld &= mask; rd &= mask; col &= mask; LD &= mask; RD &= mask
- “回転/鏡像の正規化(Jasmin)”: jasmin(ijkl, N)
- “対称重複補正”: symmetry(ijkl, N) -> {2,4,8}
- “一本化 DFS”: dfs(functionid, ld, rd, col, row, free, ...)
"""
"""
17Py_constellations_integrate_codon.py(レビュー&注釈つき)
amazon AWS m4.16xlarge x 1
$ codon build -release 15Py_constellations_optimize_codon.py && ./15Py_constellations_optimize_codon
N: Total Unique hh:mm:ss.ms
5: 10 0 0:00:00.000
6: 4 0 0:00:00.079
7: 40 0 0:00:00.001
8: 92 0 0:00:00.001
9: 352 0 0:00:00.001
10: 724 0 0:00:00.002
11: 2680 0 0:00:00.102
12: 14200 0 0:00:00.002
13: 73712 0 0:00:00.005
14: 365596 0 0:00:00.011
15: 2279184 0 0:00:00.035
16: 14772512 0 0:00:00.078
17: 95815104 0 0:00:00.436
18: 666090624 0 0:00:02.961
19: 4968057848 0 0:00:22.049
20: 39029188884 0 0:02:52.430
21: 314666222712 0 0:24:25.554
22: 2691008701644 0 3:29:33.971
23: 24233937684440 0 1 day, 8:12:58.977
workspace#suzuki$ bash MAIN.SH 15Py_constellations_optimize_codon.py
N: Total Unique hh:mm:ss.ms
17: 95815104 0 0:00:02.987
18: 666090624 0 0:00:21.549
19: 4968057848 0 0:02:43.514
workspace#suzuki$ bash MAIN.SH 17Py_constellations_integrate_codon_20251017_suzuki.py
N: Total Unique hh:mm:ss.ms
17: 95815104 0 0:00:04.037 ok
18: 666090624 0 0:00:29.301 ok
19: 4968057848 0 0:03:41.853 ok
"""
from typing import List,Set,Dict,Tuple
from datetime import datetime
StateKey=Tuple[int,int,int,int,int,int,int,int,int,int,int]
MASK64 = (1<<64)-1
class NQueens17:
def __init__(self)->None:
self.zobrist_tables: Dict[int, Dict[str, List[int]]] = {}
self.jasmin_cache: Dict[Tuple[int,int], int] = {}
# サブコンステレーション生成状態のメモ化(実行中の重複再帰を抑制)
self.subconst_cache: Set[StateKey] = set()
def mix64(self,x:int)->int:
"""splitmix64 のミキサ最終段。64bit 値 x を (>>/XOR/乗算) の 3 段で拡散して返す。 Zobrist テーブルの乱数品質を担保するために使用。"""
# MASK64:int=(1<<64)-1 # 64bit マスク(Zobrist用途)
x&=MASK64
x=(x^(x>>30))*0xBF58476D1CE4E5B9&MASK64
x=(x^(x>>27))*0x94D049BB133111EB&MASK64
x^=(x>>31)
return x&MASK64
def gen_list(self,cnt:int,seed:int)->List[int]:
"""Zobrist テーブル用の 64bit 乱数を cnt 個生成してリストで返す。 seed は splitmix64 のインクリメント規約 (0x9E3779B97F4A7C15) に従って更新。"""
# MASK64:int=(1<<64)-1 # 64bit マスク(Zobrist用途)
out:List[int]=[]
s:int=seed&MASK64
_mix64=self.mix64
for _ in range(cnt):
s=(s+0x9E3779B97F4A7C15)&MASK64 # splitmix64 のインクリメント
# out.append(self._mix64(s))
out.append(_mix64(s))
return out
def init_zobrist(self,N:int)->None:
"""Zobrist テーブルを N ごとに初期化する。キーは 'ld','rd','col','LD','RD','row','queens','k','l'。 ※ キャッシュは self.zobrist_tables[N] に格納して再利用する。"""
# MASK64:int=(1<<64)-1 # 64bit マスク(Zobrist用途)
if N in self.zobrist_tables:
return
base_seed:int=(0xC0D0_0000_0000_0000^(N<<32))&MASK64
gen_list=self.gen_list
tbl:Dict[str,List[int]]={
'ld':gen_list(N,base_seed^0x01),
'rd':gen_list(N,base_seed^0x02),
'col':gen_list(N,base_seed^0x03),
'LD':gen_list(N,base_seed^0x04),
'RD':gen_list(N,base_seed^0x05),
'row':gen_list(N,base_seed^0x06),
'queens':gen_list(N,base_seed^0x07),
'k':gen_list(N,base_seed^0x08),
'l':gen_list(N,base_seed^0x09),
}
self.zobrist_tables[N]=tbl
def zobrist_hash(self,ld:int,rd:int,col:int,row:int,queens:int,k:int,l:int,LD:int,RD:int,N:int)->int:
"""(ld, rd, col, LD, RD, row, queens, k, l) を Zobrist Hash によって 64bit にまとめる。 各マスクは N ビットに正規化してから XOR 混合する。衝突確率の低い探索済み判定に利用可能。"""
# MASK64:int=(1<<64)-1
# 1) テーブルを(必要なら)構築
self.init_zobrist(N)
# 2) 構築済みキャッシュを参照
tbl = self.zobrist_tables[N]
# zobrist_tables:Dict[int,Dict[str,List[int]]]={}
# tbl=self.init_zobrist[N]
# 3) ループ内で tbl['ld'][i] などの辞書アクセスを都度行うと遅いので、先にローカル束縛にすると微速化します:
ld_tbl, rd_tbl, col_tbl = tbl['ld'], tbl['rd'], tbl['col']
LD_tbl, RD_tbl = tbl['LD'], tbl['RD']
row_tbl, q_tbl, k_tbl, l_tbl = tbl['row'], tbl['queens'], tbl['k'], tbl['l']
# 4) N ビットへ正規化(上位ビットや負数を落とす)
mask=(1<<N)-1
ld&=mask
rd&=mask
col&=mask
LD&=mask
RD&=mask
# 5) 各ビットを見て XOR
h=0
m=ld;i=0
while i<N:
if (m&1)!=0:
# h^=tbl['ld'][i]
h^=ld_tbl[i]
m>>=1;i+=1
m=rd;i=0
while i<N:
if (m&1)!=0:
# h^=tbl['rd'][i]
h^=rd_tbl[i]
m>>=1;i+=1
m=col;i=0
while i<N:
if (m&1)!=0:
# h^=tbl['col'][i]
h^=col_tbl[i]
m>>=1;i+=1
m=LD;i=0
while i<N:
if (m&1)!=0:
# h^=tbl['LD'][i]
h^=LD_tbl[i]
m>>=1;i+=1
m=RD;i=0
while i<N:
if (m&1)!=0:
# h^=tbl['RD'][i]
h^=RD_tbl[i]
m>>=1;i+=1
# 行数・個数・強制行などスカラー要素
if 0 <= row < N: h ^= row_tbl[row]
if 0 <= queens < N: h ^= q_tbl[queens]
if 0 <= k < N: h ^= k_tbl[k]
if 0 <= l < N: h ^= l_tbl[l]
return h&MASK64
"""(i,j,k,l) を 5bit×4=20bit にパック/アンパックするユーティリティ。 mirvert は上下ミラー(行: N-1-?)+ (k,l) の入れ替えで左右ミラー相当を実現。"""
def to_ijkl(self,i:int,j:int,k:int,l:int)->int:return (i<<15)+(j<<10)+(k<<5)+l
def mirvert(self,ijkl:int,N:int)->int:return self.to_ijkl(N-1-self.geti(ijkl),N-1-self.getj(ijkl),self.getl(ijkl),self.getk(ijkl))
def ffmin(self,a:int,b:int)->int:return min(a,b)
def geti(self,ijkl:int)->int:return (ijkl>>15)&0x1F
def getj(self,ijkl:int)->int:return (ijkl>>10)&0x1F
def getk(self,ijkl:int)->int:return (ijkl>>5)&0x1F
def getl(self,ijkl:int)->int:return ijkl&0x1F
"""(i,j,k,l) パック値に対して盤面 90°/180° 回転を適用した新しいパック値を返す。 回転の定義: (r,c) -> (c, N-1-r)。対称性チェック・正規化に利用。"""
def rot90(self,ijkl:int,N:int)->int:return ((N-1-self.getk(ijkl))<<15)+((N-1-self.getl(ijkl))<<10)+(self.getj(ijkl)<<5)+self.geti(ijkl)
def rot180(self,ijkl:int,N:int)->int:return ((N-1-self.getj(ijkl))<<15)+((N-1-self.geti(ijkl))<<10)+((N-1-self.getl(ijkl))<<5)+(N-1-self.getk(ijkl))
def symmetry(self,ijkl:int,N:int)->int:return 2 if self.symmetry90(ijkl,N) else 4 if self.geti(ijkl)==N-1-self.getj(ijkl) and self.getk(ijkl)==N-1-self.getl(ijkl) else 8
def symmetry90(self,ijkl:int,N:int)->bool:return ((self.geti(ijkl)<<15)+(self.getj(ijkl)<<10)+(self.getk(ijkl)<<5)+self.getl(ijkl))==(((N-1-self.getk(ijkl))<<15)+((N-1-self.getl(ijkl))<<10)+(self.getj(ijkl)<<5)+self.geti(ijkl))
"""与えた (i,j,k,l) の 90/180/270° 回転形が既出集合 ijkl_list に含まれるかを判定する。"""
def check_rotations(self,ijkl_list:Set[int],i:int,j:int,k:int,l:int,N:int)->bool:return any(rot in ijkl_list for rot in [((N-1-k)<<15)+((N-1-l)<<10)+(j<<5)+i,((N-1-j)<<15)+((N-1-i)<<10)+((N-1-l)<<5)+(N-1-k),(l<<15)+(k<<10)+((N-1-i)<<5)+(N-1-j)])
def get_jasmin(self,c:int,N:int)->int:
""" Jasmin 正規化のキャッシュ付ラッパ。盤面パック値 c を回転/ミラーで規約化した代表値を返す。
※ キャッシュは self.jasmin_cache[(c,N)] に保持。
[Opt-08] キャッシュ付き jasmin() のラッパー """
# jasmin_cache:Dict[Tuple[int,int],int]={}
key=(c,N)
if key in self.jasmin_cache:
return self.jasmin_cache[key]
result=self.jasmin(c,N)
self.jasmin_cache[key]=result
return result
def jasmin(self,ijkl:int,N:int)->int:
# 最初の最小値と引数を設定
arg=0
min_val=self.ffmin(self.getj(ijkl),N-1-self.getj(ijkl))
# i: 最初の行(上端) 90度回転2回
if self.ffmin(self.geti(ijkl),N-1-self.geti(ijkl))<min_val:
arg=2
min_val=self.ffmin(self.geti(ijkl),N-1-self.geti(ijkl))
# k: 最初の列(左端) 90度回転3回
if self.ffmin(self.getk(ijkl),N-1-self.getk(ijkl))<min_val:
arg=3
min_val=self.ffmin(self.getk(ijkl),N-1-self.getk(ijkl))
# l: 最後の列(右端) 90度回転1回
if self.ffmin(self.getl(ijkl),N-1-self.getl(ijkl))<min_val:
arg=1
min_val=self.ffmin(self.getl(ijkl),N-1-self.getl(ijkl))
# 90度回転を arg 回繰り返す
_rot90=self.rot90
for _ in range(arg):
# ijkl=self.rot90(ijkl,N)
ijkl=_rot90(ijkl,N)
# 必要に応じて垂直方向のミラーリングを実行
if self.getj(ijkl)<N-1-self.getj(ijkl):
ijkl=self.mirvert(ijkl,N)
return ijkl
def dfs(self,functionid:int,ld:int,rd:int,col:int,row:int,free:int,jmark:int,endmark:int,mark1:int,mark2:int,board_mask:int,blockK_by_funcid:List[int],blockl_by_funcid:List[int],func_meta:List[Tuple[int,int,int]],N:int,N1:int,NK:int,NJ:int)->int:
"""汎用 DFS カーネル。古い SQ???? 関数群を 1 本化し、func_meta の記述に従って
(1) mark 行での step=2/3 + 追加ブロック、(2) jmark 特殊、(3) ゴール判定、(4) +1 先読み
を切り替える。引数:
functionid: 現在の分岐モード ID(次の ID, パターン, 先読み有無は func_meta で決定)
ld/rd/col: 斜線/列の占有
row/free: 現在行と空きビット
jmark/endmark/mark1/mark2: 特殊行/探索終端
board_mask: 盤面全域のビットマスク
blockK_by_funcid/blockl_by_funcid: 関数 ID に紐づく追加ブロック
func_meta: (next_id, funcptn, availptn) のメタ情報配列
"""
# ローカル束縛(属性/関数参照を減らす)
_dfs=self.dfs
next_funcid,funcptn,avail_flag=func_meta[functionid]
bit:int=0
avail:int=free
total:int=0
step:int=1
add1:int=0
row_step:int=row+step
use_blocks:bool=False # blockK/blockl を噛ませるか
use_future:bool=(avail_flag==1) # _should_go_plus1 を使うか
blockK:int=0
blockl:int=0
local_next_funcid:int=functionid # 既定は自分
# ==============================
# ここから 1 本化した共通配置ループ # 既定値(通常の +1 前進ループ)
# ==============================
# --- P1/P2/P3: mark 行での step=2/3 + block 適用を共通ループへ設定
if funcptn in (0,1,2):
at_mark:bool=(row==mark1) if funcptn in (0,2) else (row==mark2)
if at_mark and avail:
step:int=2 if funcptn in (0,1) else 3
add1:int=1 if (funcptn==1 and functionid==20) else 0 # SQd1BlB のときだけ1
row_step=row+step
blockK:int=blockK_by_funcid[functionid]
blockl:int=blockl_by_funcid[functionid]
use_blocks:bool=True
use_future:bool=False # ここは従来どおり next_free だけで分岐
local_next_funcid=next_funcid
# ---- P6: endmark 基底
elif funcptn==5 and row==endmark:
if functionid==14:# SQd2B
return 1 if (avail&(~1))>0 else 0
return 1
# --- P4: jmark 特殊を共通ループの前処理に畳み込む
elif funcptn==3 and row==jmark:
avail&=~1 # 列0禁止
ld|=1 # 左斜線 LSB を立てる
local_next_funcid=next_funcid # 次関数へ
# ---- P5: N-1-jmark 入口(行据え置きの一手前処理)
elif funcptn==4:
if row==N1-jmark:
rd|=NJ # rd |= 1 << N1
next_free:int=board_mask&~((ld<<1)|(rd>>1)|col)
if next_free:
total+=_dfs(next_funcid,ld<<1,rd>>1,col,row,next_free,jmark,endmark,mark1,mark2,board_mask,blockK_by_funcid,blockl_by_funcid,func_meta,N,N1,NK,NJ)
return total # 続行(通常ループへ)
#use_blocks / use_future の分岐ごとにループを分ける
# ブーリアン分岐を内側ループに残すより、「使う/使わない」でループを2本に分けると分岐予測も最短経路になります
if use_blocks:
while avail:
bit:int=avail&-avail
avail&=avail-1
next_ld,next_rd,next_col=(((ld|bit)<<step)|add1)|blockl,((rd|bit)>>step)|blockK,col|bit
next_free:int=board_mask&~(next_ld|next_rd|next_col)
if next_free:
total+=_dfs(local_next_funcid,next_ld,next_rd,next_col,row_step,next_free,jmark,endmark,mark1,mark2,board_mask,blockK_by_funcid,blockl_by_funcid,func_meta,N,N1,NK,NJ)
else:
# “素の +1” だけ(先読みなし) # “+1 with 先読み”
if use_future:
while avail:
bit:int=avail&-avail
avail&=avail-1
next_ld,next_rd,next_col=((ld|bit)<<step)|add1,(rd|bit)>>step,col|bit
next_free:int=board_mask&~(next_ld|next_rd|next_col)
if not next_free:
continue
if row_step>=endmark:
total+=_dfs(local_next_funcid,next_ld,next_rd,next_col,row_step,next_free,jmark,endmark,mark1,mark2,board_mask,blockK_by_funcid,blockl_by_funcid,func_meta,N,N1,NK,NJ)
continue
# 先読み(+1)の中
m1=1 if row_step==mark1 else 0
m2=1 if row_step==mark2 else 0
use_j=(funcptn==4) # ★ P5ファミリのみ J 行を有効化
mj=1 if (use_j and row_step==(N1-jmark)) else 0
extra=((m1|m2)*NK)|(mj*NJ)
future=board_mask&~(((next_ld<<1)|(next_rd>>1)|next_col)|extra)
if future:
total+=_dfs(local_next_funcid,next_ld,next_rd,next_col,row_step,next_free,jmark,endmark,mark1,mark2,board_mask,blockK_by_funcid,blockl_by_funcid,func_meta,N,N1,NK,NJ)
else:
while avail:
bit:int=avail&-avail
avail&=avail-1
next_ld,next_rd,next_col=((ld|bit)<<step)|add1,(rd|bit)>>step,col|bit
next_free:int=board_mask&~(next_ld|next_rd|next_col)
if next_free:
total+=_dfs(local_next_funcid,next_ld,next_rd,next_col,row_step,next_free,jmark,endmark,mark1,mark2,board_mask,blockK_by_funcid,blockl_by_funcid,func_meta,N,N1,NK,NJ)
return total
def exec_solutions(self,constellations:List[Dict[str,int]],N:int)->None:
"""各 Constellation(部分盤面)ごとに最適分岐(functionid)を選び、`dfs()` で解数を取得。 結果は `solutions` に書き込み、最後に `symmetry()` の重みで補正する。前段で SoA 展開し 並列化区間のループ体を軽量化。"""
N2:int=N-2
small_mask:int=(1<<N2)-1
board_mask:int=(1<<N)-1
dfs=self.dfs
symmetry=self.symmetry
getj,getk,getl=self.getj,self.getk,self.getl
FUNC_CATEGORY={
# N-3
"SQBkBlBjrB":3,"SQBlkBjrB":3,"SQBkBjrB":3,
"SQd2BkBlB":3,"SQd2BkB":3,"SQd2BlkB":3,
"SQd1BkBlB":3,"SQd1BlkB":3,"SQd1BkB":3,"SQd0BkB":3,
# N-4
"SQBklBjrB":4,"SQd2BklB":4,"SQd1BklB":4,
# 0(上記以外)
"SQBlBjrB":0,"SQBjrB":0,"SQB":0,"SQBlBkBjrB":0,
"SQBjlBkBlBjrB":0,"SQBjlBklBjrB":0,"SQBjlBlBkBjrB":0,"SQBjlBlkBjrB":0,
"SQd2BlB":0,"SQd2B":0,"SQd2BlBkB":0,
"SQd1BlB":0,"SQd1B":0,"SQd1BlBkB":0,"SQd0B":0
}
FID={
"SQBkBlBjrB":0,"SQBlBjrB":1,"SQBjrB":2,"SQB":3,
"SQBklBjrB":4,"SQBlBkBjrB":5,"SQBkBjrB":6,"SQBlkBjrB":7,
"SQBjlBkBlBjrB":8,"SQBjlBklBjrB":9,"SQBjlBlBkBjrB":10,"SQBjlBlkBjrB":11,
"SQd2BkBlB":12,"SQd2BlB":13,"SQd2B":14,"SQd2BklB":15,"SQd2BlBkB":16,
"SQd2BkB":17,"SQd2BlkB":18,"SQd1BkBlB":19,"SQd1BlB":20,"SQd1B":21,
"SQd1BklB":22,"SQd1BlBkB":23,"SQd1BlkB":24,"SQd1BkB":25,"SQd0B":26,"SQd0BkB":27
}
# next_funcid, funcptn, availptn の3つだけ持つ
func_meta=[
(1,0,0),# 0 SQBkBlBjrB -> P1, 先読みなし
(2,1,0),# 1 SQBlBjrB -> P2, 先読みなし
(3,3,1),# 2 SQBjrB -> P4, 先読みあり
(3,5,1),# 3 SQB -> P6, 先読みあり
(2,2,0),# 4 SQBklBjrB -> P3, 先読みなし
(6,0,0),# 5 SQBlBkBjrB -> P1, 先読みなし
(2,1,0),# 6 SQBkBjrB -> P2, 先読みなし
(2,2,0),# 7 SQBlkBjrB -> P3, 先読みなし
(0,4,1),# 8 SQBjlBkBlBjrB-> P5, 先読みあり
(4,4,1),# 9 SQBjlBklBjrB -> P5, 先読みあり
(5,4,1),# 10 SQBjlBlBkBjrB-> P5, 先読みあり
(7,4,1),# 11 SQBjlBlkBjrB -> P5, 先読みあり
(13,0,0),# 12 SQd2BkBlB -> P1, 先読みなし
(14,1,0),# 13 SQd2BlB -> P2, 先読みなし
(14,5,1),# 14 SQd2B -> P6, 先読みあり(avail 特例)
(14,2,0),# 15 SQd2BklB -> P3, 先読みなし
(17,0,0),# 16 SQd2BlBkB -> P1, 先読みなし
(14,1,0),# 17 SQd2BkB -> P2, 先読みなし
(14,2,0),# 18 SQd2BlkB -> P3, 先読みなし
(20,0,0),# 19 SQd1BkBlB -> P1, 先読みなし
(21,1,0),# 20 SQd1BlB -> P2, 先読みなし(add1=1 は dfs 内で特別扱い)
(21,5,1),# 21 SQd1B -> P6, 先読みあり
(21,2,0),# 22 SQd1BklB -> P3, 先読みなし
(25,0,0),# 23 SQd1BlBkB -> P1, 先読みなし
(21,2,0),# 24 SQd1BlkB -> P3, 先読みなし
(21,1,0),# 25 SQd1BkB -> P2, 先読みなし
(26,5,1),# 26 SQd0B -> P6, 先読みあり
(26,0,0),# 27 SQd0BkB -> P1, 先読みなし
]
n3=1<<max(0,N-3) # 念のため負シフト防止
n4=1<<max(0,N-4)
size=max(FID.values())+1
blockK_by_funcid=[0]*size
blockl_by_funcid=[0,1,0,0,1,1,0,2,0,0,0,0,0,1,0,1,1,0,2,0,0,0,1,1,2,0,0,0]
for fn,cat in FUNC_CATEGORY.items():# FUNC_CATEGORY: {関数名: 3 or 4 or 0}
fid=FID[fn]
blockK_by_funcid[fid]=n3 if cat==3 else (n4 if cat==4 else 0)
# ===== 前処理ステージ(単一スレッド) =====
m=len(constellations)
# SoA(Structure of Arrays)に展開:並列本体が軽くなる
ld_arr=[0]*m;rd_arr=[0]*m;col_arr=[0]*m
row_arr=[0]*m;free_arr=[0]*m
jmark_arr=[0]*m;end_arr=[0]*m
mark1_arr=[0]*m;mark2_arr=[0]*m
funcid_arr=[0]*m
ijkl_arr=[0]*m # symmetry 計算用
N1=N-1
NK=1<<(N-3)
NJ=1<<N1
results=[0]*m
target:int=0
for i,constellation in enumerate(constellations):
jmark=mark1=mark2=0
start_ijkl=constellation["startijkl"]
start=start_ijkl>>20
ijkl=start_ijkl&((1<<20)-1)
j,k,l=getj(ijkl),getk(ijkl),getl(ijkl)
ld,rd,col=constellation["ld"]>>1,constellation["rd"]>>1,(constellation["col"]>>1)|(~small_mask)
LD=(1<<(N1-j))|(1<<(N1-l))
ld|=LD>>(N-start)
if start>k:
rd|=(1<<(N1-(start-k+1)))
if j>=2*N-33-start:
rd|=(1<<(N1-j))<<(N2-start)
free=~(ld|rd|col)
if j<(N-3):
jmark,endmark=j+1,N2
if j>2*N-34-start:
if k<l:
mark1,mark2=k-1,l-1
if start<l:
if start<k:
if l!=k+1:target=0 # SQBkBlBjrB
else:target=4 # SQBklBjrB
else:target=1 #_SQBlBjrB
else:target=2 # SQBjrB
else:
mark1,mark2=l-1,k-1
if start<k:
if start<l:
if k!=l+1:target=5 # SQBlBkBjrB
else:target=7 # SQBlkBjrB
else:target=6 # SQBkBjrB
else:target=2 # SQBjrB
else:
if k<l:
mark1,mark2=k-1,l-1
if l!=k+1:target=8 # SQBjlBkBlBjrB
else:target=9 # SQBjlBklBjrB
else:
mark1,mark2=l-1,k-1
if k!=l+1:target=10 # SQBjlBlBkBjrB
else:target=11 # SQBjlBlkBjrB
elif j==(N-3):
endmark=N2
if k<l:
mark1,mark2=k-1,l-1
if start<l:
if start<k:
if l!=k+1:target=12 # SQd2BkBlB
else:target=15 # SQd2BklB
else:
mark2=l-1
target=13 # SQd2BlB
else:target=14 # SQd2B
else:
mark1,mark2=l-1,k-1
if start<k:
if start<l:
if k!=l+1:target=16 # SQd2BlBkB
else:target=18 # SQd2BlkB
else:
mark2=k-1
target=17 # SQd2BkB
else:target=14 # SQd2B
elif j==N2:# jがコーナーから1列内側
if k<l:
endmark=N2
if start<l:
if start<k:
mark1=k-1
if l!=k+1:
mark2=l-1
target=19 # SQd1BkBlB
else:target=22 # SQd1BklB
else:
mark2=l-1
target=20 # SQd1BlB
else:target=21 # SQd1B
else:# l < k
if start<k:
if start<l:
if k<N2:
mark1,endmark=l-1,N2
if k!=l+1:
mark2=k-1
target=23 # SQd1BlBkB
else:target=24 # SQd1BlkB
else:
if l!=(N-3):
mark2,endmark=l-1,N-3
target=20 # SQd1BlB
else:
endmark=N-4
target=21 # SQd1B
else:
if k!=N2:
mark2,endmark=k-1,N2
target=25 # SQd1BkB
else:
endmark=N-3
target=21 # SQd1B
else:
endmark=N2
target=21 # SQd1B
else:# j がコーナー
endmark=N2
if start>k:
target=26 # SQd0B
else:
mark1=k-1
target=27 # SQd0BkB
# 配列へ格納
ld_arr[i],rd_arr[i],col_arr[i]=ld,rd,col
row_arr[i],free_arr[i]=start,free
jmark_arr[i],end_arr[i]=jmark,endmark
mark1_arr[i],mark2_arr[i]=mark1,mark2
funcid_arr[i]=target
ijkl_arr[i]=ijkl
# ===== 並列ステージ:計算だけ =====
# cnt=dfs(target,ld,rd,col,start,free,jmark,endmark,mark1,mark2,board_mask,blockK_by_funcid,blockl_by_funcid,func_meta,N)
@par
for i in range(m):
cnt=dfs(funcid_arr[i],ld_arr[i],rd_arr[i],col_arr[i],row_arr[i],free_arr[i],jmark_arr[i],end_arr[i],mark1_arr[i],mark2_arr[i],board_mask,blockK_by_funcid,blockl_by_funcid,func_meta,N,N1,NK,NJ)
results[i]=cnt*symmetry(ijkl_arr[i],N)
# ===== 書き戻し(単一スレッド) =====
# constellation["solutions"]=cnt*symmetry(ijkl,N)
for i,constellation in enumerate(constellations):
constellation["solutions"]=results[i]
def set_pre_queens_cached(self,ld:int,rd:int,col:int,k:int,l:int,row:int,queens:int,LD:int,RD:int,counter:List[int],constellations:List[Dict[str,int]],N:int,preset_queens:int,visited:Set[int],constellation_signatures:Set[Tuple[int,int,int,int,int,int]])->None:
"""サブコンステレーション生成のキャッシュ付ラッパ。StateKey で一意化し、 同一状態での重複再帰を回避して生成量を抑制する。"""
key:StateKey=(ld,rd,col,k,l,row,queens,LD,RD,N,preset_queens)
# subconst_cache:Set[StateKey]=set()
# インスタンス共有キャッシュを使う(ローカル初期化しない!)
sc = self.subconst_cache
if key in sc:
return
# ここで登録してから本体を呼ぶと、並行再入の重複も抑止できる
sc.add(key)
# if key in subconst_cache:
# # 以前に同じ状態で生成済み → 何もしない(または再利用)
# return
# 新規実行(従来通りset_pre_queensの本体処理へ)
self.set_pre_queens(ld,rd,col,k,l,row,queens,LD,RD,counter,constellations,N,preset_queens,visited,constellation_signatures)
# subconst_cache[key] = True # マークだけでOK
# subconst_cache.add(key)
def set_pre_queens(self,ld:int,rd:int,col:int,k:int,l:int,row:int,queens:int,LD:int,RD:int,counter:list,constellations:List[Dict[str,int]],N:int,preset_queens:int,visited:Set[int],constellation_signatures:Set[Tuple[int,int,int,int,int,int]])->None:
"""事前に置く行 (k,l) を強制しつつ、queens==preset_queens に到達するまで再帰列挙。 `visited` には軽量な `state_hash` を入れて枝刈り。到達時は {ld,rd,col,startijkl} を constellation に追加。"""
mask=(1<<N)-1 # setPreQueensで使用
# ---------------------------------------------------------------------
# 状態ハッシュによる探索枝の枝刈り バックトラック系の冒頭に追加 やりすぎると解が合わない
# <>zobrist_hash
# 各ビットを見てテーブルから XOR するため O(N)(ld/rd/col/LD/RDそれぞれで最大 N 回)。
# とはいえ N≤17 なのでコストは小さめ。衝突耐性は高い。
# マスク漏れや負数の扱いを誤ると不一致が起きる点に注意(先ほどの & ((1<<N)-1) 修正で解決)。
# h: int = self.zobrist_hash(ld, rd, col, row, queens, k, l, LD, RD, N)
# <>state_hash
# その場で数個の ^ と << を混ぜるだけの O(1) 計算。
# 生成されるキーも 単一の int なので、set/dict の操作が最速&省メモリ。
# ただし理論上は衝突し得ます(実際はN≤17の範囲なら実害が出にくい設計にしていればOK)。
# [Opt-09] Zobrist Hash(Opt-09)の導入とその用途
# ビットボード設計でも、「盤面のハッシュ」→「探索済みフラグ」で枝刈りは可能です。
state_hash:int=(ld<<3)^(rd<<2)^(col<<1)^row^(queens<<7)^(k<<12)^(l<<17)^(LD<<22)^(RD<<27)^(N<<1)
h:int=state_hash
if h in visited:
return
visited.add(h)
# <>StateKey(タプル)
# 11個の整数オブジェクトを束ねるため、オブジェクト生成・GC負荷・ハッシュ合成が最も重い。
# set の比較・保持も重く、メモリも一番食います。
# 衝突はほぼ心配ないものの、速度とメモリ効率は最下位。
# key: StateKey = (ld, rd, col, row, queens, k, l, LD, RD)
# if key in visited:
# return
# visited.add(key)
# ---------------------------------------------------------------------
# k行とl行はスキップ
if row==k or row==l:
self.set_pre_queens_cached(ld<<1,rd>>1,col,k,l,row+1,queens,LD,RD,counter,constellations,N,preset_queens,visited,constellation_signatures)
return
# クイーンの数がpreset_queensに達した場合、現在の状態を保存
if queens==preset_queens:
# signatureの生成
signature=(ld,rd,col,k,l,row) # 必要な変数でOK
if not hasattr(self,"constellation_signatures"):
constellation_signatures=set()
signatures=constellation_signatures
if signature not in signatures:
constellation={"ld":ld,"rd":rd,"col":col,"startijkl":row<<20,"solutions":0}
constellations.append(constellation) #星座データ追加
signatures.add(signature)
counter[0]+=1
return
# 現在の行にクイーンを配置できる位置を計算
free=~(ld|rd|col|(LD>>(N-1-row))|(RD<<(N-1-row)))&mask
_set_pre_queens_cached=self.set_pre_queens_cached
while free:
bit:int=free&-free
free&=free-1
_set_pre_queens_cached((ld|bit)<<1,(rd|bit)>>1,col|bit,k,l,row+1,queens+1,LD,RD,counter,constellations,N,preset_queens,visited,constellation_signatures)
def gen_constellations(self,ijkl_list:Set[int],constellations:List[Dict[str,int]],N:int,preset_queens:int)->None:
"""開始コンステレーション(代表部分盤面)の列挙。中央列(奇数 N)特例、回転重複排除 (`check_rotations`)、Jasmin 正規化(`get_jasmin`)を経て、各 sc から `set_pre_queens_cached` でサブ構成を作る。"""
# 実行ごとにメモ化をリセット(N や preset_queens が変わるとキーも変わるが、
# 長時間プロセスなら念のためクリアしておくのが安全)
self.subconst_cache.clear()
halfN=(N+1)//2 # Nの半分を切り上げ
N1:int=N-1
constellation_signatures: Set[ Tuple[int, int, int, int, int, int] ] = set()
# --- [Opt-03] 中央列特別処理(奇数Nの場合のみ) ---
if N%2==1:
center=N//2
ijkl_list.update(
self.to_ijkl(i,j,center,l)
for l in range(center+1,N1)
for i in range(center+1,N1)
if i!=(N1)-l
for j in range(N-center-2,0,-1)
if j!=i and j!=l
if not self.check_rotations(ijkl_list,i,j,center,l,N)
)
# --- [Opt-03] 中央列特別処理(奇数Nの場合のみ) ---
# コーナーにクイーンがいない場合の開始コンステレーションを計算する
ijkl_list.update(self.to_ijkl(i,j,k,l) for k in range(1,halfN) for l in range(k+1,N1) for i in range(k+1,N-1) if i!=(N-1)-l for j in range(N-k-2,0,-1) if j!=i and j!=l if not self.check_rotations(ijkl_list,i,j,k,l,N))
# コーナーにクイーンがある場合の開始コンステレーションを計算する
ijkl_list.update({self.to_ijkl(0,j,0,l) for j in range(1,N-2) for l in range(j+1,N1)})
# Jasmin変換
ijkl_list={self.get_jasmin(c,N) for c in ijkl_list}
L=1<<(N1) # Lは左端に1を立てる
# ローカルアクセスに変更
geti,getj,getk,getl=self.geti,self.getj,self.getk,self.getl
to_ijkl=self.to_ijkl
_set_pre_queens_cached=self.set_pre_queens_cached
for sc in ijkl_list:
# ここで毎回クリア(=この sc だけの重複抑止に限定)
constellation_signatures=set()
i,j,k,l=geti(sc),getj(sc),getk(sc),getl(sc)
Lj=L>>j;Li=L>>i;Ll=L>>l
ld=((L>>(i-1)) if i>0 else 0)|(1<<(N-k))
rd=(L>>(i+1))|(1<<(l-1))
col=1|L|Li|Lj
LD=Lj|Ll
RD=Lj|(1<<k)
counter:List[int]=[0] # サブコンステレーションを生成
visited:Set[int]=set()
_set_pre_queens_cached(ld,rd,col,k,l,1,3 if j==N1 else 4,LD,RD,counter,constellations,N,preset_queens,visited,constellation_signatures)
current_size=len(constellations)
base=to_ijkl(i,j,k,l)
for a in range(counter[0]):
constellations[-1-a]["startijkl"]|=base
"""constellations の各要素が {ld, rd, col, startijkl} を全て持つかを検証する。"""
def validate_constellation_list(self,constellations:List[Dict[str,int]])->bool: return all(all(k in c for k in ("ld","rd","col","startijkl")) for c in constellations)
"""32bit little-endian の相互変換ヘルパ。Codon/CPython の差異に注意。"""
def read_uint32_le(self,b:str)->int: return (ord(b[0])&0xFF)|((ord(b[1])&0xFF)<<8)|((ord(b[2])&0xFF)<<16)|((ord(b[3])&0xFF)<<24)
def int_to_le_bytes(self,x:int)->List[int]: return [(x>>(8*i))&0xFF for i in range(4)]
def file_exists(self,fname:str)->bool:
"""ファイル存在チェック(読み取り open の可否で判定)。"""
try:
with open(fname,"rb"):
return True
except:
return False
def validate_bin_file(self,fname:str)->bool:
"""bin キャッシュのサイズ妥当性確認(1 レコード 16 バイトの整数倍か)。"""
try:
with open(fname,"rb") as f:
f.seek(0,2) # ファイル末尾に移動
size=f.tell()
return size%16==0
except:
return False
"""テキスト形式で constellations を保存/復元する(1 行 5 数値: ld rd col startijkl solutions)。"""
def save_constellations_txt(self,path:str,constellations:List[Dict[str,int]])->None:
with open(path,"w") as f:
for c in constellations:
ld=c["ld"]
rd=c["rd"]
col=c["col"]
startijkl=c["startijkl"]
solutions=c.get("solutions",0)
f.write(f"{ld} {rd} {col} {startijkl} {solutions}\n")
"""テキスト形式で constellations を保存/復元する(1 行 5 数値: ld rd col startijkl solutions)。"""
def load_constellations_txt(self,path:str)->List[Dict[str,int]]:
out:List[Dict[str,int]]=[]
with open(path,"r") as f:
for line in f:
parts=line.strip().split()
if len(parts)!=5:
continue
ld=int(parts[0]);rd=int(parts[1]);col=int(parts[2])
startijkl=int(parts[3]);solutions=int(parts[4])
out.append({"ld":ld,"rd":rd,"col":col,"startijkl":startijkl,"solutions": solutions})
return out
"""テキストキャッシュを読み込み。壊れていれば `gen_constellations()` で再生成して保存する。"""
def load_or_build_constellations_txt(self,ijkl_list:Set[int],constellations,N:int,preset_queens:int)->List[Dict[str,int]]:
# N と preset_queens に基づいて一意のファイル名を構成
fname=f"constellations_N{N}_{preset_queens}.txt"
# ファイルが存在すれば読み込むが、破損チェックも行う
if self.file_exists(fname):
try:
constellations=self.load_constellations_txt(fname)
if self.validate_constellation_list(constellations):
return constellations
else:
print(f"[警告] 不正なキャッシュ形式: {fname} を再生成します")
except Exception as e:
print(f"[警告] キャッシュ読み込み失敗: {fname}, 理由: {e}")
# ファイルがなければ生成・保存
constellations:List[Dict[str,int]]=[]
self.gen_constellations(ijkl_list,constellations,N,preset_queens)
self.save_constellations_txt(fname,constellations)
return constellations
"""bin 形式で constellations を保存/復元。Codon では str をバイト列として扱う前提のため、CPython では bytes で書き込むよう分岐/注意が必要。"""
def save_constellations_bin(self,fname:str,constellations:List[Dict[str,int]])->None:
_int_to_le_bytes=self.int_to_le_bytes
with open(fname,"wb") as f:
for d in constellations:
for key in ["ld","rd","col","startijkl"]:
b=_int_to_le_bytes(d[key])
_int_to_le_bytes(d[key])
f.write("".join(chr(c) for c in b)) # Codonでは str がバイト文字列扱い
"""bin 形式で constellations を保存/復元。Codon では str をバイト列として扱う前提のため、CPython では bytes で書き込むよう分岐/注意が必要。"""
def load_constellations_bin(self,fname:str)->List[Dict[str,int]]:
constellations:List[Dict[str,int]]=[]
_read_uint32_le=self.read_uint32_le
with open(fname,"rb") as f:
while True:
raw=f.read(16)
if len(raw)<16:
break
ld=self.read_uint32_le(raw[0:4])
rd=self.read_uint32_le(raw[4:8])
col=self.read_uint32_le(raw[8:12])
startijkl=_read_uint32_le(raw[12:16])
constellations.append({ "ld":ld,"rd":rd,"col":col,"startijkl":startijkl,"solutions":0 })
return constellations
def load_or_build_constellations_bin(self,ijkl_list:Set[int],constellations,N:int,preset_queens:int)->List[Dict[str,int]]:
"""bin キャッシュを読み込み。検証に失敗した場合は再生成して保存し、その結果を返す。"""
fname=f"constellations_N{N}_{preset_queens}.bin"
if self.file_exists(fname):
constellations=self.load_constellations_bin(fname)
# ファイルが存在すれば読み込むが、破損チェックも行う
try:
constellations=self.load_constellations_bin(fname)
if self.validate_bin_file(fname) and self.validate_constellation_list(constellations):
return constellations
else:
print(f"[警告] 不正なキャッシュ形式: {fname} を再生成します")
except Exception as e:
print(f"[警告] キャッシュ読み込み失敗: {fname}, 理由: {e}")
# ファイルがなければ生成・保存
constellations:List[Dict[str,int]]=[]
self.gen_constellations(ijkl_list,constellations,N,preset_queens)
self.save_constellations_bin(fname,constellations)
return constellations
class NQueens17_constellations():
# 小さなNは正攻法で数える(対称重みなし・全列挙)
def _bit_total(self,size:int)->int:
"""小さな N 用の素朴な全列挙(対称重みなし)。ビットボードで列/斜線の占有を管理して再帰的に合計を返す。検算/フォールバック用。"""
mask=(1<<size)-1
total=0
def bt(row:int,left:int,down:int,right:int):
nonlocal total
if row==size:
total+=1
return
bitmap=mask&~(left|down|right)
while bitmap:
bit=-bitmap&bitmap
bitmap^=bit
bt(row+1,(left|bit)<<1,down|bit,(right|bit)>>1)
bt(0,0,0,0)
return total
def main(self)->None:
"""N=5..17 の合計解を計測。N<=5 は `_bit_total()` のフォールバック、それ以外は星座キャッシュ(.bin/.txt)→ `exec_solutions()` → 合計→既知解 `expected` と照合。"""
nmin:int=5
nmax:int=18
preset_queens:int=4 # 必要に応じて変更
print(" N: Total Unique hh:mm:ss.ms")
for size in range(nmin,nmax):
start_time=datetime.now()
if size<=5:
# ← フォールバック:N=5はここで正しい10を得る
total=self._bit_total(size)
dt=datetime.now()-start_time
text=str(dt)[:-3]
print(f"{size:2d}:{total:13d}{0:13d}{text:>20s}")
continue
ijkl_list:Set[int]=set()
constellations:List[Dict[str,int]]=[]
# NQ=NQueens17(size)
NQ=NQueens17()
#---------------------------------
# 星座リストそのものをキャッシュ
#---------------------------------
# キャッシュを使わない
NQ.gen_constellations(ijkl_list,constellations,size,preset_queens)
# キャッシュを使う、キャッシュの整合性もチェック
# -- txt
# constellations = NQ.load_or_build_constellations_txt(ijkl_list,constellations, size, preset_queens)
# -- bin
# constellations = NQ.load_or_build_constellations_bin(ijkl_list,constellations, size, preset_queens)
#---------------------------------
NQ.exec_solutions(constellations,size)
total:int=sum(c['solutions'] for c in constellations if c['solutions']>0)
time_elapsed=datetime.now()-start_time
text=str(time_elapsed)[:-3]
# print(f"{size:2d}:{total:13d}{0:13d}{text:>20s}")
expected:List[int]=[0,0,0,0,0,10,4,40,92,352,724,2680,14200,73712,365596,2279184,14772512,95815104,666090624,4968057848]
status:str="ok" if expected[size]==total else f"ng({total}!={expected[size]})"
print(f"{size:2d}:{total:13d}{0:13d}{text:>20s} {status}")
if __name__=="__main__":
NQueens17_constellations().main()
📚 関連リンク
Nクイーン問題 過去記事アーカイブ
【過去記事アーカイブ】Nクイーン問題 過去記事一覧
https://suzukiiichiro.github.io/search/?keyword=Nクイーン問題
【Github】エイト・クイーンのソース置き場 BashもJavaもPythonも!
https://github.com/suzukiiichiro/N-Queens
Nクイーン問題(101)Python/Codonで爆速プログラミング コンステレーション+インテグレート
https://suzukiiichiro.github.io/posts/2025-10-27-17-n-queens-suzuki/
Nクイーン問題(100)Python/Codonで爆速プログラミング コンステレーション+マージ
https://suzukiiichiro.github.io/posts/2025-10-27-16-n-queens-suzuki/
Nクイーン問題(99)Python/Codonで爆速プログラミング コンステレーション+最適化
https://suzukiiichiro.github.io/posts/2025-10-27-15-n-queens-suzuki/
Nクイーン問題(98)Python/Codonで爆速プログラミング コンステレーション+並列処理
https://suzukiiichiro.github.io/posts/2025-10-27-14-n-queens-suzuki/
Nクイーン問題(97)Python/Codonで爆速プログラミング コンステレーション
https://suzukiiichiro.github.io/posts/2025-10-27-13-n-queens-suzuki/
Nクイーン問題(96)Python/Codonで爆速プログラミング キャリーチェーン
https://suzukiiichiro.github.io/posts/2025-10-27-12-n-queens-suzuki/
Nクイーン問題(95)Python/Codonで爆速プログラミング ノードレイヤー+対象解除法
https://suzukiiichiro.github.io/posts/2025-10-27-11-n-queens-suzuki/
Nクイーン問題(94)Python/Codonで爆速プログラミング ノードレイヤー+ミラー
https://suzukiiichiro.github.io/posts/2025-10-27-10-n-queens-suzuki/
Nクイーン問題(93)Python/Codonで爆速プログラミング ノードレイヤー
https://suzukiiichiro.github.io/posts/2025-10-27-09-n-queens-suzuki/
Nクイーン問題(92)Python/Codonで爆速プログラミング ビットでミラー+対象解除法
https://suzukiiichiro.github.io/posts/2025-10-27-08-n-queens-suzuki/
Nクイーン問題(91)Python/Codonで爆速プログラミング ビットで対象解除法
https://suzukiiichiro.github.io/posts/2025-10-27-07-n-queens-suzuki/
Nクイーン問題(90)Python/Codonで爆速プログラミング ビットでミラー
https://suzukiiichiro.github.io/posts/2025-10-27-06-n-queens-suzuki/
Nクイーン問題(89)Python/Codonで爆速プログラミング ビットでバックトラック
https://suzukiiichiro.github.io/posts/2025-10-27-05-n-queens-suzuki/
Nクイーン問題(88)Python/Codonで爆速プログラミング 対象解除法
https://suzukiiichiro.github.io/posts/2025-10-27-04-n-queens-suzuki/
Nクイーン問題(87)Python/Codonで爆速プログラミング バックトラック
https://suzukiiichiro.github.io/posts/2025-10-27-03-n-queens-suzuki/
Nクイーン問題(86)Python/Codonで爆速プログラミング ポストフラグ
https://suzukiiichiro.github.io/posts/2025-10-27-02-n-queens-suzuki/
Nクイーン問題(85)Python/Codonで爆速プログラミング ブルートフォース
https://suzukiiichiro.github.io/posts/2025-10-27-01-n-queens-suzuki/
Nクイーン問題(84)Python/Codonで爆速プログラミング
https://suzukiiichiro.github.io/posts/2025-10-24-01-n-queens-suzuki/
Nクイーン問題(83)Python-codon&並列処理で高速化 Constellations
https://suzukiiichiro.github.io/posts/2025-03-11-07-n-queens-suzuki/
Nクイーン問題(82)Python-並列処理で高速化 16Python_carryChain_ProcessPool
https://suzukiiichiro.github.io/posts/2025-03-11-06-n-queens-suzuki/
Nクイーン問題(81)Python-codonで高速化 15Python_carryChain
https://suzukiiichiro.github.io/posts/2025-03-11-05-n-queens-suzuki/
Nクイーン問題(80)Python-並列処理で高速化 14Python_NodeLayer_symmetry_ProcessPool
https://suzukiiichiro.github.io/posts/2025-03-11-04-n-queens-suzuki/
Nクイーン問題(79)Python-codonで高速化 13Python_NodeLayer_symmetry
https://suzukiiichiro.github.io/posts/2025-03-11-03-n-queens-suzuki/
Nクイーン問題(78)Python-codonで高速化 12Python_NodeLayer_mirror
https://suzukiiichiro.github.io/posts/2025-03-11-02-n-queens-suzuki/
Nクイーン問題(77)Python-codonで高速化 11Python_NodeLayer
https://suzukiiichiro.github.io/posts/2025-03-11-01-n-queens-suzuki/
Nクイーン問題(76)Python-並列処理で高速化 10Python_bit_symmetry_ProcessPool
https://suzukiiichiro.github.io/posts/2025-03-10-05-n-queens-suzuki/
Nクイーン問題(75)Python-並列処理で高速化 09Python_bit_symmetry_ThreadPool
https://suzukiiichiro.github.io/posts/2025-03-10-04-n-queens-suzuki/
Nクイーン問題(74)Python-codonで高速化 08Python_bit_symmetry
https://suzukiiichiro.github.io/posts/2025-03-10-03-n-queens-suzuki/
Nクイーン問題(73)Python-codonで高速化 07Python_bit_mirror
https://suzukiiichiro.github.io/posts/2025-03-10-02-n-queens-suzuki/
Nクイーン問題(72)Python-codonで高速化 06Python_bit_backTrack
https://suzukiiichiro.github.io/posts/2025-03-10-01-n-queens-suzuki/
Nクイーン問題(71)Python-codonで高速化 05Python_optimize
https://suzukiiichiro.github.io/posts/2025-03-07-01-n-queens-suzuki/
Nクイーン問題(70)Python-codonで高速化 04Python_symmetry
https://suzukiiichiro.github.io/posts/2025-03-06-02-n-queens-suzuki/
Nクイーン問題(69)Python-codonで高速化 03Python_backTracking
https://suzukiiichiro.github.io/posts/2025-03-06-01-n-queens-suzuki/
Nクイーン問題(68)Python-codonで高速化 02Python_postFlag
https://suzukiiichiro.github.io/posts/2025-03-05-03-n-queens-suzuki/
Nクイーン問題(67)Python-codonで高速化 01Python_bluteForce
https://suzukiiichiro.github.io/posts/2025-03-05-02-n-queens-suzuki/
Nクイーン問題(66)Python-codonで高速化
https://suzukiiichiro.github.io/posts/2025-03-05-01-n-queens-suzuki/
Nクイーン問題(65) N25を解決!事実上の日本一に
https://suzukiiichiro.github.io/posts/2024-04-25-01-n-queens-suzuki/
Nクイーン問題(64)第七章 並列処理 キャリーチェーン NVIDIA CUDA編
https://suzukiiichiro.github.io/posts/2023-08-01-05-n-queens-suzuki/
Nクイーン問題(63)第七章 並列処理 キャリーチェーン NVIDIA CUDA編
https://suzukiiichiro.github.io/posts/2023-08-01-05-n-queens-suzuki/
Nクイーン問題(62)第七章 並列処理 対称解除法 ビットボード NVIDIA CUDA編
https://suzukiiichiro.github.io/posts/2023-08-01-04-n-queens-suzuki/
Nクイーン問題(61)第七章 並列処理 対称解除法 ノードレイヤー NVIDIA CUDA編
https://suzukiiichiro.github.io/posts/2023-08-01-03-n-queens-suzuki/
Nクイーン問題(60)第七章 並列処理 ミラー NVIDIA CUDA編
https://suzukiiichiro.github.io/posts/2023-08-01-02-n-queens-suzuki/
Nクイーン問題(59)第七章 並列処理 ビットマップ NVIDIA CUDA編
https://suzukiiichiro.github.io/posts/2023-08-01-01-n-queens-suzuki/
Nクイーン問題(58)第六章 並列処理 pthread C言語編
https://suzukiiichiro.github.io/posts/2023-06-28-09-n-queens-suzuki/
Nクイーン問題(57)第八章 キャリーチェーン C言語編
https://suzukiiichiro.github.io/posts/2023-06-28-08-n-queens-suzuki/
Nクイーン問題(56)第八章 ミラー C言語編
https://suzukiiichiro.github.io/posts/2023-06-28-06-n-queens-suzuki/
Nクイーン問題(55)第八章 ビットマップ C言語編
https://suzukiiichiro.github.io/posts/2023-06-28-05-n-queens-suzuki/
Nクイーン問題(54)第八章 ビットマップ C言語編
https://suzukiiichiro.github.io/posts/2023-06-28-04-n-queens-suzuki/
Nクイーン問題(53)第八章 配置フラグ C言語編
https://suzukiiichiro.github.io/posts/2023-06-28-03-n-queens-suzuki/
Nクイーン問題(52)第八章 バックトラック C言語編
https://suzukiiichiro.github.io/posts/2023-06-28-02-n-queens-suzuki/
Nクイーン問題(51)第八章 ブルートフォース C言語編
https://suzukiiichiro.github.io/posts/2023-06-28-01-n-queens-suzuki/
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/