¿Inteligencia artificial o computación cuántica para resolver problemas de Sudoku?

Como juego de mesa interesante, después del centenario del nacimiento de Sudoku, ¿cómo se convirtió en uno de los puntos focales de la investigación informática? Explora cómo utilizar la inteligencia artificial o las computadoras cuánticas para crear un solucionador de Sudoku inteligente desde cero.

Antes de profundizar más, aprendamos sobre la historia

Mark Bloch dijo: "La historia se llama la madre de las disciplinas". Entonces, hablemos de cómo nació el famoso juego de Sudoku. Esta historia se remonta a finales del siglo XIX y se originó en Francia. El diario francés "Le Siecle" (Le Siecle) lanzó un juego de adivinanzas de tamaño 9x9 que requiere operaciones aritméticas en lugar de operaciones lógicas. Sus números son de dos dígitos en lugar de 1-9. La naturaleza de su juego es similar al Sudoku, es decir, sumando los números en las líneas horizontal, columna y diagonal también obtendrá el mismo número. En 1979, el arquitecto y rompecabezas retirado Howard Garns es considerado el creador del moderno juego de Sudoku, que se publicó por primera vez en Dell Magazine con el nombre de un lugar digital. En 1986, una empresa japonesa de rompecabezas llamada Nikoli publicó este rompecabezas por primera vez con el nombre de Sudoku.

En el marco de la resolución del problema del juego de Sudoku.

Sudoku es un ejemplo real del problema de satisfacción de restricciones (CSP) porque el conjunto de variables, el conjunto de dominios y el conjunto de restricciones son todos limitados. Tenemos que ingresar un número entre 1-9 en una tabla de 9x9, de modo que cada fila, columna y subtabla de 3x3 contenga solo un número. Existe otra variación de Sudoku, a saber, Sudoku diagonal, que prescribe un conjunto de restricciones adicionales en cada diagonal de la diagonal de la mesa, y cada número debe tener exactamente una característica. Sabemos que las restricciones satisfacen el dominio, la solución óptima debe satisfacer todas las restricciones, o más específicamente, debe obedecer las reglas del juego. La solución óptima satisfará todas las restricciones del conjunto, resolviendo así el problema.

Computacionalmente, puede usar tiempo polinomial no determinista (NP) para resolver las restricciones de resolver Sudoku, porque se pueden usar algunos algoritmos de fuerza bruta muy especiales para resolver las restricciones, y la validez del conjunto de soluciones también se puede probar en tiempo polinomial, donde la entrada El problema está relacionado con un conjunto de soluciones para la longitud del polinomio. El Sudoku completamente resuelto es un ejemplo de una cuadrícula latina (como dijo Euler, la matriz nxn está llena de n símbolos diferentes). El problema del Sudoku se puede considerar como un problema de coloración de gráficos, donde solo necesitamos usar 9 colores para colorear los gráficos, y las letras desnudas se pueden considerar como colores parciales.

Utilice conjuntos de algoritmos de inteligencia artificial para satisfacer las limitaciones

El principio básico de la ciencia computacional es la capacidad de confiar en la lógica para satisfacer ciertas restricciones. Al resolver problemas de Sudoku, debemos entrenar al solucionador para que encuentre algunos modos ganadores específicos además de las reglas básicas. Por tanto, el problema es que el sistema no solo sigue ciegamente las reglas, sino que también toma algunas decisiones considerando sus efectos a corto y largo plazo. Estos patrones se denominan heurística. Al igual que los jugadores expertos que encontraron el conocimiento y las habilidades del juego, solo comprender las reglas básicas no los convierte en expertos en juegos. Por lo tanto, cuando desarrollamos algoritmos y resolvemos problemas, debemos tener en cuenta heurísticas útiles, y también debemos incluirlas en el programa para que sea más inteligente y útil cuando ganemos.

Para nuestro Sudoku Solver, ingresaremos una secuencia de 81 números como una cadena y usaremos '. '(Punto) indica números sin resolver. Para resolver este problema, reemplazamos "." Con todos los números posibles que se pueden colocar en la celda.

De acuerdo con las limitaciones del Sudoku, no podemos usar un número varias veces en una fila, columna o subcuadrado de 3x3 cerca de cualquier celda. En el caso del Sudoku diagonal, también debemos considerar las mismas restricciones. Primero reemplazamos los puntos con todos los números posibles del 1 al 9. Usamos la siguiente función grid_values ​​para hacer esto programáticamente.

# 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. 

Primero asigne todos los valores posibles en todas las celdas no resueltas.

Ahora, hemos reemplazado las celdas no resueltas con todos los números posibles entre 1 y 9. De las reglas básicas del Sudoku, sabemos que si los números se han usado en la fila, columna y subcampo 3x3, no podemos Úselo dos veces. Así que eliminémoslos, si los encontramos en la cuadrícula sin resolver, inicialmente los hemos llenado con todos los números posibles. Entonces, echemos un vistazo a cómo usar el método de eliminación de Python para eliminar esos números irrelevantes de las unidades no resueltas.

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.

Por lo tanto, mientras se satisfacen estas restricciones, a veces nos encontraremos con algunas celdas que solo pueden colocar un número, pero a excepción de este número, otros números ya no son factibles para esta celda en particular. Primero tenemos que completar estos contenidos. Hay una solución adecuada. A esto lo llamamos la "única opción", y es el método heurístico más simple para resolver celdas de cuadrícula de Sudoku.

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

En el proceso que rodea la satisfacción de restricciones hasta ahora, puede surgir la siguiente situación: habrá dos píxeles sin resolver en una celda (considerando filas, columnas y subcuadrados de 3x3), de los cuales solo se pueden asignar dos números específicos restantes. Por lo tanto, estos dos números se pueden eliminar efectivamente de los números posibles en otras celdas de la misma celda. Este método heurístico se llama gemelos desnudos. La implementación de este algoritmo hizo específicamente una copia profunda de los valores de la cuadrícula y verificó la viabilidad de los gemelos desnudos, es decir, si hay dos píxeles sin resolver que solo pueden aceptar dos valores específicos. Si es factible, continuará fusionándose Elimine las celdas de la misma celda de estos dos valores de los otros dos valores. Usamos la función nude_twins que se muestra a continuación para implementarla programáticamente:

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

Ahora, intentamos reducir el problema tanto como sea posible aplicando repetidamente estos tres algoritmos de satisfacción de restricciones y verificando si está atascado y no se puede reducir más. Hacemos esto programáticamente usando la función reduce_puzzle. Lo que tenemos que hacer es llamar a las tres primeras funciones en el bucle for y terminar la función cuando el número de unidades resueltas en la secuencia de entrada y salida del valor de la cuadrícula sea el mismo, lo que significa que no se puede reducir más. Solo el algoritmo de satisfacción de restricciones .

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. 	

Si la cuadrícula de Sudoku aún no se resuelve mediante el problema de satisfacción de la restricción, parte de la solución llegará a la salida y algunas de las celdas aún se asignarán a algunos valores posibles. En este caso, lo que tenemos que hacer es usar el árbol de búsqueda para buscar el mejor conjunto de números en esas ubicaciones. Usamos el algoritmo de búsqueda en profundidad (DFS) para recorrer el árbol de búsqueda. Entonces, básicamente, usando DFS, creamos varias instancias con la misma cuadrícula y probamos diferentes asignaciones posibles para cada unidad no resuelta. De forma recursiva pedimos al algoritmo CSP que reduzca la cuadrícula en función de los resultados de la búsqueda. Lo implementamos programáticamente de la siguiente manera:

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		

Usamos la función mostrar sudoku para mostrar la secuencia de la cadena de entrada como una cuadrícula de Sudoku de 9x9 bidimensional:

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

Para resolver la secuencia de Sudoku, llamamos a la función anterior de la siguiente manera:

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)

El resultado se muestra a continuación, donde un conjunto de algoritmos ha calculado con éxito la respuesta.

Un enfoque cuántico para resolver el Sudoku como un problema de satisfacción con restricciones

Ahora, intentaremos resolver una cuadrícula de Sudoku simple usando "recocido cuántico simulado". En primer lugar, ¿qué es el recocido simulado? Para tales problemas de optimización, nuestra idea es utilizar algunos algoritmos heurísticos subóptimos y obtener el conjunto óptimo de algoritmos heurísticos para obtener la solución óptima. Usamos el modelo DWave AQC (computación cuántica para la diabetes) aquí para muestrear la mejor solución que cumpla con las restricciones discutidas anteriormente. ...

Utilice el muestreador híbrido DWave Kerberos:

En este ejemplo, estamos usando el solucionador híbrido incluido con DWave. Realiza una búsqueda paralela para encontrar la mejor heurística. Es un solucionador híbrido porque utiliza tanto la computación cuántica como las características de la computación clásica. También es un muestreador de descomposición que utiliza un flujo de trabajo asincrónico en el procesamiento. Está incluido en el paquete de software Ocean SDK de DWave Systems. Para iniciar el desarrollo local, asegúrese de que Python 3.5+ esté instalado en su sistema y luego emita el siguiente comando.

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

Utilice el modelo cuadrático binario (BQM) para el cálculo

No podemos construir restricciones que estén directamente listas para ser enviadas a Computadoras Cuánticas, necesitamos una representación intermedia para alimentarlas. Es por eso que usaremos BQM. Afortunadamente, DWave Ocean SDK ya proporciona una herramienta llamada "composición" que puede usarse para reducir los problemas de satisfacción de restricciones a BQM. En primer lugar, como sugiere el nombre, el modelo cuadrático binario en sí mismo es un sistema de ecuaciones, es cuadrático y se expresa en binario. Debido a la mayor complejidad de los cálculos, las computadoras Quantum usan estos cálculos para acelerar enormemente el proceso de desarrollo. Por lo tanto, en el juego, decidimos usar la herramienta de combinación de dimod, que devolverá un modelo cuadrático binario, que es el más pequeño para cada una de las k combinaciones de sus variables de entrada y variables internas.

Primero importamos los paquetes de software necesarios desde dwave-ocean-sdk y realizamos algunas comprobaciones de integridad antes de leerlos en Sudoku Grid.

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

Ahora, usamos la combinación de todas las variables disponibles del índice de fila, columna y subcuadrado de Sudoku Grid, y usamos la herramienta de combinación para crear un modelo cuadrático binario.

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()

Eso es. Hemos implementado con éxito dos soluciones inteligentes, una de las cuales usa cálculos clásicos y usa un algoritmo heurístico de inteligencia artificial muy poderoso, que incluso puede resolver cuadrículas Sudoku diagonales. El segundo método utiliza un muestreador heurístico híbrido asíncrono, que también utiliza el recocido simulado del modelo de cálculo cuántico adiabático para convertir el problema de satisfacción de restricciones en un modelo cuadrático binario para muestrearlo, obteniendo así la mejor solución de muestreo.

Autor: Swastik Nath

equipo de traducción de deephub

Supongo que te gusta

Origin blog.csdn.net/m0_46510245/article/details/108766461
Recomendado
Clasificación