数独の問題を解決するための人工知能または量子コンピューティング?

興味深いボードゲームとして、数独の生誕100周年後、どのようにしてコンピューティング研究の焦点の1つになりましたか?人工知能または量子コンピューターを使用してインテリジェントな数独ソルバーをゼロから作成する方法を探ってください。

深く掘り下げる前に、歴史について学びましょう

マーク・ブロッホ氏は、「歴史は学問の母と呼ばれている。」では、有名な数独ゲームがどのようにして誕生したかについて話しましょう。この物語は、19世紀の終わりまで遡ることができ、フランスで始まりました。フランスの日刊紙「ルシークル」(Le Siecle)は、論理演算ではなく算術演算を必要とする9x9サイズの推測ゲームをリリースしました。その数字は1〜9ではなく2桁です。そのゲームの性質は数独に似ています。つまり、水平線、列線、対角線に数字を追加すると、同じ数字になります。1979年、引退した建築家で謎を解くハワードガーンズは、デジタルプレイスの名前でDell Magazineに最初に発表された現代の数独ゲームの作成者と見なされます。1986年に、ニコリという日本のパズル会社がこのパズルを数独という名前で初めて公開しました。

数独ゲームの問題を解決する枠組みの中で

数独は、変数セット、ドメインセット、および制約セットがすべて制限されているため、制約充足問題(CSP)の実例です。9x9テーブルには1〜9の数値を入力する必要があるため、各行、列、3x3サブテーブルには1つの数値のみが含まれます。数独には別のバリエーションがあります。それは、対角線の数独です。これは、表の対角線の各対角線に追加の制約のセットを規定し、各数値は1つの特性のみを持つ必要があります。制約はドメインを満たし、最適なソリューションはすべての制約を満たさなければならないことを知っています。具体的には、ゲームのルールに従う必要があります。最適な解は、セット内のすべての制約を満たすため、問題が解決されます。

計算的には、非決定論的多項式時間(NP)を使用して数独を解くための制約を解くことができます。これは、いくつかの非常に特殊な総当たりアルゴリズムを使用して制約を解くことができ、解集合の有効性を多項式時間でテストすることもできます。問題は、多項式の長さの一連の解に関連しています。完全に解決された数独はラテン語グリッドの例です(オイラーが言ったように、nxn配列はn個の異なるシンボルで埋められます)。数独の問題は、グラフィックのカラーリングの問題と考えることができます。この場合、グラフィックのカラーリングには9色を使用するだけでよく、裸の文字は部分的なカラーと見なすことができます。

人工知能アルゴリズムセットを使用して制約を満たす

計算科学の基本原理は、特定の制約を満たすためにロジックに依存する能力です。数独の問題を解く場合、基本的なルールに加えて、特定の勝利モードを見つけるようにソルバーをトレーニングする必要があります。したがって、問題は、システムが盲目的にルールに従うだけでなく、短期的および長期的な影響を考慮しながらいくつかの決定を行うことです。これらのパターンはヒューリスティックと呼ばれます。ゲームの知識とスキルに出会ったエキスパートプレーヤーと同様に、基本的なルールを理解するだけではゲームのエキスパートにはなりません。したがって、アルゴリズムを開発して問題を解決するときは、有用なヒューリスティックを念頭に置いておく必要があります。また、プログラムにアルゴリズムを含めて、勝ったときにそれをよりスマートでより有用にする必要があります。

数独ソルバーでは、81個の数字のシーケンスを文字列として入力して使用します。'(ピリオド)は、未解決の数値を示します。この問題を解決するには、「。」をセルに配置できるすべての可能な数字に置き換えます。

数独の制限により、セルの近くの行、列、または3x3サブスクエアで複数回使用することはできません。対角の数独の場合も、同じ制約を考慮する必要があります。最初に、ピリオドを1〜9のすべての可能な数字に置き換えます。プログラムでこれを行うには、次のgrid_values関数を使用します。

# For the sake of caluclation we take rows as alphaneumeric and columns as numeric. 
rows = 'ABCDEFGHI' 
columns = '123456789'
boxes = [r + c for r in rows for c in columns]  #every possible cell combination in the grid.

def grid_values(grid):
  """
  Take in the Unsolved Sudoku Sequence and replaces the unsolved boxes initially with all
  possible values which can get into that cell. Lastly returns a dictionary containing the 
  values at all cell positions along with cells. 
  """
  values = []
  every_digits = '123456789'
  for n in grid:
    if c == '.':   # replacing every unsolved value with every possible value initially.
      values.append(every_digits)
    else:          # if already solved, causing it no change. 
      values.append(c)
   assert len(values) == 81
   return dict(zip(boxes, values)) #returning the sudoku grid with all possible cell values. 

まず、すべての未解決のセルにすべての可能な値を割り当てます。

これで、未解決のセルを1〜9のすべての可能な数字に置き換えました。数独の基本ルールから、行、列、3x3サブフィールドで数字が使用されている場合、 2回使用してください。したがって、それらを排除してみましょう。未解決のグリッドでそれらに遭遇した場合、最初にすべての可能な数でそれらを埋めました。それでは、エリミネーションpythonメソッドを使用して、未解決のユニットからこれらの無関係な数値を削除する方法を見てみましょう。

columns_reversed = columns[::-1] #reversing the columns for calculating the Diagonal Units.

def make_combinations(m, n):
  """
  Takes in input of generally iterables and creates all possible combintation out of them.
  args:
   a: string iterable
   b: string iterable
   return : list of all possible combination. 
  """
  return [x + y for x in m for y in n]

row_units = [make_combinations(r, columns) for r in rows]
column_units = [make_combinations(rows, c) for c in columns]
sub_square_units = [make_combinations(m, n) for m in ('ABC', 'DEF', 'GHI') 
                    for n in ('123','456','789')]
diagonal_1_units = [[rows[i]+columns[i] for i in range(len(rows))]]
diagonal_2_units = [[rows[i]+columns_reversed[i] for i in range(len(rows))]]
diagonal_units = diagonal_1_units + diagonal_2_units
all_units = row_units + column_units + square_units + diagonal_units
units = dict((b, [u for u in all_units if b in u]) for b in boxes)
peers = dict((b, set(sum(units[b], [])) - {b}) for b in boxes)

def eliminate(values):
  """
  Eliminate the redundant numbers from the unsolved cells if the number already appeared once 
  in the peer of the current cell. 
  What we do here is we erase that redundant number from the unsolved value cells if appeared once. 
  """
  solved_cells = [box for box in values.keys() if len(values[box]) == 1] # cell is solved if there's only one digit
  for box in solved_cells:
    value_at_cell = values[box]   # retrieve the current value at that cell.
    for peer in peers[box]:       # check for the cell's peers if the value appears again.
      values[peer] = values[peer].replace(value_at_cell, '')
   return values   # return the modified values dictionary.

したがって、これらの制約を満たしている間、1つの数値しか配置できないいくつかのセルに遭遇することがありますが、この数値を除いて、他の数値はこの特定のセルでは実行できなくなります。これらの内容を最初に入力する必要があります。適切な解決策があります。これを「唯一の選択」と呼び、数独グリッドセルを解くための最も簡単なヒューリスティック手法です。

def only_choice(values):
  """
  If in order to satisfy the constraints of the Sudoku Puzzle there is only a single viable option
  we fill in the Cell with that option only and thereby obtain a solve for the cell. 
  
  """
  for unit in all_units:     #searching across all the vicinity of the cell.
    for digit in '123456789':  
      to_be_filled = [cell for cell in unit if unit in values[unit]]
      if len(to_be_filled) == 1:  # if there exists only a single cell in the unit which is not solved
        values[to_be_filled[0]] = digit  # We fill in the cell with its proper answer.    
  return values

これまでの制約充足を取り巻くプロセスでは、次の状況が発生する可能性があります。セルに2つの未解決のピクセルがあり(行、列、および3x3サブスクエアを考慮)、そのうち2つの特定の残りの数のみを割り当てることができます。したがって、これらの2つの数値は、同じセル内の他のセルの可能な数値から効果的に削除できます。この発見的方法は裸の双子と呼ばれます。このアルゴリズムの実装は、グリッド値の詳細なコピーを具体的に作成し、ネイキッドツインの実現可能性、つまり、2つの特定の値のみを受け入れることができる未解決のピクセルが2つあるかどうかを確認しました。可能であれば、マージを続行しますこれら2つの値の同じセル内のセルを他の2つの値から削除します。以下に示すnude_twins関数を使用して、プログラムで実装します。

def naked_twins(values):
	"""
	If there are two unsolved cells in a same unit exist such that it can only be filled by only 
	two specific digits, then those two digits can be safely removed from all other cells in the same unit.
	"""
	twins_possible = [unit for unit in values.keys() if len(values[unit]) == 2]  
	twins = [[unit1, unit2] for unit1 in twins_possible for unit2 in peers[unit1] 
					 if set(values[unit1]) == (set(values[unit2]))]    #confimed Naked Twins
	for twin in twins:
		unit1 = twin[0] 
		unit2 = twin[2]
		peers1 = set(peers[unit1])  
		peers2 = set(peers[unit2])
		common_peers = peers1 & peers2  #finding the intersection between the peers of the two naked twin element
		for peer in common_peers:
			if len(values[peer]) >  1:
				for value in values[unit1]:
					values[peer] = values[peer].replace(val, ''))  # Erasing the values. 
	return values

ここで、これらの3つの制約充足アルゴリズムを繰り返し適用し、それがスタックしてさらに削減できないかどうかを確認することにより、問題をできるだけ減らすようにしています。プログラムでは、reduce_puzzle関数を使用してこれを行います。forループの最初の3つの関数を呼び出し、グリッド値の入力シーケンスと出力シーケンスで解決されたユニットの数が同じになったときに関数を終了する必要があります。これは、それ以上削減できないことを意味します。制約充足アルゴリズムのみ。

def reduce_puzzle(values):
	"""
	Applying the 4 Constraint Satisfaction Algorithms until it is not further reducible. 
	Checking if the Number of Solved Cells between the iteration.
	"""
	solved_values = [unit for unit in values.keys() if len(values[unit]) == 1] # considering solved cells
	stuck = False #boolean flag to determine the end of loop
	while not stuck:
		prev_solved_values = len([unit for unit in values.keys() if len(values[unit]) == 1]) #checkpoint 1
		values = eliminate(values) # applying Elimination CSP
		values = only_choice(values) # applying Only Choice CSP
		values = naked_twins(values)  # applying Naked Twins CSP
		after_solved_values = len([unit for unit in values.keys() if len(values[unit]) == 1])
		stuck = after_solved_values == prev_solved_values  # Getting out of loop is the number of solved cell is still the same as the previous iteration.
		
		if len([unit for unit in values.keys() if len(values[unit]) == 0]):
			return False   # if there's problems in the internal representation of the sudoku grid return False. 
	return values			# return the reduced grid values. 	

それでも数独グリッドが制約充足問題によって解決されない場合、解の一部が出力に到達し、一部のセルは依然としていくつかの可能な値に割り当てられます。この場合、私たちがしなければならないことは、検索ツリーを使用して、それらの場所で最適な数のセットを検索することです。深さ優先検索(DFS)アルゴリズムを使用して、検索ツリーをトラバースします。したがって、基本的には、DFSを使用して、同じグリッドで複数のインスタンスを作成し、未解決のユニットごとに異なる可能な割り当てを試しました。検索結果に基づいてグリッドを減らすようにCSPアルゴリズムに再帰的に要求します。次のようにプログラムで実装します。

def search(values):
	"""
	Recursive Depth-First Search: If the sudoku grid is not further reducible by constraint satisfaction
	a few of the cells will be left with different options and with DFS with search for the optimal 
	values for those yet-unsolved cells.  
	"""
	values = reduce_puzzle(values) # We call the Reduction Function to reduce the puzzle further based on the search results across iterations. 
	if values is False:
		return False
	if all(len(values[b]) == 1 for b in boxes):
		print("Sudoku Problem Solved!")
		return values
	m, n = min((len(values[b]), b) for b in boxes if len(values[b]) > 1)
	for value in values[n]:
		new_sudoku = values.copy()
		new_sudoku[n] = value
		attempted = search(new_sudoku)
		if attempted:
			return attempted		

display sudoku関数を使用して、入力文字列シーケンスを2次元の9x9数独グリッドとして表示します。

def display(values):
    """
    Display the values as a 2-D grid.
    Input: The sudoku in dictionary form
    """
    width = 1 + max(len(values[b]) for b in boxes)
    line = '+'.join(['-' * (width * 3)] * 3)
    for r in rows:
        print(''.join(values[r + c].center(width) + ('|' if c in '36' else '')
                      for c in cols))
        if r in 'CF':
            print(line)
    return

数独シーケンスを解決するために、上記の関数を次のように呼び出します。

if __name__ == "__main__":
    diag_sudoku_grid = '2.............62....1....7...6..8...3...9...7...6..4...4....8....52.............3'
    values = grid_values(diag_sudoku_grid)
    values = reduce_puzzle(values)
    values = search(values)
    display(values)

出力を以下に示します。ここでは、一連のアルゴリズムが応答を正常に計算しました。

制約充足問題として数独を解くための量子的アプローチ

ここで、「量子シミュレーテッドアニーリング」を使用して単純な数独グリッドを解こうとします。まず、シミュレーテッドアニーリングとは何ですか?このような最適化問題の場合、私たちのアイデアは、次善のヒューリスティックアルゴリズムを使用して、最適なソリューションを取得するために最適なヒューリスティックアルゴリズムのセットを取得することです。ここでは、DWave AQCモデル(糖尿病の量子計算)を使用して、前述の制約を満たす最適なソリューションをサンプリングします。

DWave Kerberosハイブリッドサンプラーを使用します。

この例では、DWaveに含まれているハイブリッドソルバーを使用しています。並列検索を実行して、最良のヒューリスティックを見つけます。量子コンピューティング機能と従来のコンピューティング機能の両方を使用するため、ハイブリッドソルバーです。また、処理に非同期ワークフローを使用する分解サンプラーでもあります。DWave SystemsのOcean SDKソフトウェアパッケージに含まれています。ローカル開発を開始するには、Python 3.5+がシステムにインストールされていることを確認してから、次のコマンドを発行します。

python -m pip install --upgrade pip
pip install dwave-ocean-sdk

計算にバイナリ二次モデル(BQM)を使用する

Quantum Computersに直接供給する準備ができている制約を構築することはできません。それらを供給するための中間表現が必要です。これが、BQMを使用する理由です。幸い、DWave Ocean SDKには、BQMに対する制約充足問題を削減するために使用できる「コンポジション」と呼ばれるツールがすでに用意されています。まず、名前が示すように、バイナリ二次モデル自体は方程式系であり、二次式であり、バイナリで表現されます。計算はより複雑であるため、Quantumコンピュータはこれらの計算を使用して開発プロセスを大幅にスピードアップします。したがって、ゲームでは、dimodの組み合わせツールを使用することにしました。これは、入力変数と内部変数のk個の組み合わせのそれぞれに対して最小のバイナリ2次モデルを返します。

最初にdwave-ocean-sdkから必要なソフトウェアパッケージをインポートし、実際に数独グリッドに読み込む前に整合性チェックを実行します。

import dimod  
import math 
import sys
import dimod.generators.constraints import combinations
from hybrid.reference import KerberosSampler

def prettify(row, col, digit):
    return "{row}, {col}_{digit}".format(row, col, digit)

def read_matrix(filename):
    with open(filename, 'r') as f:
        all_lines = f.read()
    lines = []
    for line in all_lines:
        new_line = line.rstrip()
        if new_line:
            new_line = list(map(int, new_line.split(' ')))
            lines.append(new_line)
    return lines 


def sanity_check(matrix):
    n = len(matrix)
    m = int(math.sqrt(n))
    unique_digits = set(range(1, 1+n))

    for row in matrix:
        if set(row) != unique_digits:
            print("Error in row", row)
            return false
    for j in range(n):
        col = [matrix[i][j] for i in range(n)]
        if set(col) != unique_digits:
            print("Error in column", col)

    subsquare_idx = [(i, j) for i in range(m) for j in range(m)]
    for r_scalar in range(m):
        for c_scalar in range(m):
            subsquare = [matrix[i + r_scalar * m ][j + c_scalar * m] for i, j in subsquare_idx]
            if set(subsquare) != unique_digits:
                print('Error in sub-square', subsquare)
                return True

    return True

ここで、数独グリッドの行、列、およびサブスクエアインデックスのすべての使用可能な変数の組み合わせを使用し、組み合わせツールを使用してバイナリ2次モデルを作成します。

def main():

    if len(sys.argv) > 1:
        filename = sys.argv[1]
        
    matrix = read_matrix(filename)
    n = len(matrix)
    m = int(math.sqrt(n))
    digits = range(1, n+1)

    bqm = dimod.BinaryQuadraticModel({}, {}, 0.0, dimod.SPIN)

    for row in range(n):
        for col in range(n):
            node_digits = [prettify(row, col, digit) for digit in digits]
            one_digit_bqm = combinations(node_digits, 1)
            bqm.update(one_digit_bqm)

    for row in range(n):
        for digit in digits:
            row_nodes = [prettify(row, col, digit) for col in range(n)]
            row_bqm = combinations(row_nodes, 1)
            bqm.update(row_bqm)
    for col in range(n):
        for digit in digits:
            col_nodes = [prettify(row, col, digit) for row in range(n)]
            col_bqm = combinations(col_nodes, 1)
            bqm.update(col_bqm)


if __name__ == "__main__":
    main()

それでおしまい。2つのスマートソリューションを正常に実装しました。1つは古典的な計算を使用し、非常に強力な人工知能ヒューリスティックアルゴリズムを使用して、対数の数独グリッドを解決することもできます。2番目の方法では、非同期ハイブリッドヒューリスティックサンプラーを使用します。これは、断熱量子計算モデルのシミュレートされたアニーリングを使用して、制約充足問題をバイナリ2次モデルに変換し、サンプリングして、最適なサンプリングソリューションを取得します。

作成者:Swastik Nath

ディープハブ翻訳チーム

おすすめ

転載: blog.csdn.net/m0_46510245/article/details/108766461