Algoritmo para generar todos los conjuntos de puntos m en una red cúbica nxnxn que son únicos bajo simetría

10

Estoy implementando un algoritmo que será bastante complejo desde el punto de vista computacional, y quiero intentar asegurarme de que no estoy haciendo un trabajo innecesario.

Hay una red cúbica nxnxn, por ejemplo, si n = 2, esto consiste en (0,0,0), (0,1,0), (1,0,0), (1,1,0), (0, 1,1), (0,0,1), (1,0,1), (1,1,1).

A partir de esta red, generaré recursivamente todos los conjuntos de puntos m, algo así como:

solve(set_of_points) {
     if set_of_points.size = m, finish

     do some useful computation here

     for each point in lattice not in set_of_points:
         solve(set_of_points + new_point);
}

Esto se puede llamar comenzando con un conjunto vacío de puntos.

La naturaleza del problema es tal que en realidad no necesito todas las permutaciones de m puntos, solo las que son únicas bajo las simetrías naturales del cubo.

Por ejemplo, tome un cubo de 2x2x2 y suponga que queremos todos los conjuntos de 1 punto. Bajo el algoritmo básico anterior, hay 8 conjuntos diferentes de 1 punto.

Sin embargo, usando las simetrías del cubo podemos reducir esto a 1 conjunto único de 1 puntos, ya que todos los 8 originales son equivalentes bajo las simetrías del cubo (en este caso, todas son 'esquinas').

Si el cubo es 2x2x2 ym = 2, hay 28 conjuntos en el algoritmo básico, pero esto se reduce a solo 3 bajo simetría (por ejemplo, {(0,0,0), (1,0,0)}, {(0 , 0,0), (1,1,0)}, {(0,0,0), (1,1,1)})

Obviamente, hacer el cálculo en 3 conjuntos de puntos es mucho mejor que 28, así que mi pregunta es ¿cómo hago para no generar conjuntos de puntos que sean simétricamente equivalentes a un conjunto ya generado? O si esto no es posible, ¿cómo puedo al menos reducir un poco la cantidad de series?

(Nota: si m = 1 esto es relativamente fácil, simplemente elija los puntos que están más cerca de (0,0,0) que cualquiera de los otros vértices, con un poco de dulce de azúcar en los límites. Es para m> 1 que esto ser un problema real)

rbennett485
fuente
1
Por simétricamente equivalente, usted incluye qué operaciones: ¿Planos espejo a través del centro? Punto de inversión a través del centro? ¿Los tres ejes de 4 rotaciones a través del centro?
BmyGuest
Cualquier isometría haría el truco
rbennett485
Si todavía estás cerca, ¿se permitiría la repetición en el conjunto de puntos m? Por ejemplo, para m = 3, ¿se considera {(0,0,0), (1,1,1), (0,0,0)} como una selección válida?
blackpen
@blackpen no, tiene que ser 3 puntos únicos
rbennett485

Respuestas:

1

Concepto basico:

(1) Podemos ver el punto (0,0,0) simplemente como 000. Cada punto en la red ahora cae en una secuencia simple. El primer punto es 000, luego 001, luego 010 011 100 101 110 y 111. Este es el orden en que intentará agregarlos al conjunto de puntos.

(2) Del mismo modo, el conjunto {(0,0,0), (0,0,1), (0,1,0)} puede verse simplemente como 000001010, y el conjunto {(0,0,0) , (0,1,0), (0,0,1)} simplemente pueden verse como 000010001. Dos conjuntos diferentes no pueden tener la misma secuencia, y puede ver fácilmente 000001010 como numérica o alfabéticamente menor que 000010001. Llamemos a esto el valor establecido. Cada conjunto posible de N puntos ahora tiene un valor establecido, y todos los conjuntos posibles de N puntos ahora se incluyen en una lista simple y bien ordenada.

(3) Cada grupo isomorfo de conjuntos de puntos tiene exactamente un miembro que tendrá el valor de conjunto más bajo. Esos son los únicos donde realmente hacemos "cómputo útil".

(4) Aquí está la parte que necesitará un trabajo significativo. Antes de ejecutar resolver (set_of_points + new_point), desea ver si algún isomorfismo reduciría el valor establecido para set_of_points + new_point. Si algún isomorfismo reduciría el valor establecido, entonces este NO es un miembro de menor valor del conjunto isomorfo. Omitimos hacer cualquier trabajo en este nuevo_punto. También estamos omitiendo todo el trabajo recursivo que habríamos hecho dentro de esta resolución (set_of_points, candidato_point).

solve(set_of_points,new_point) {
 set_of_points = set_of_points + new_point
 do some useful computation here
 if set_of_points.size = m, compute how many isomophisms exist, apply that multiple, finish
 for(candidate_point = new_point+1 to last_point) { /skips point-permutations for free!/
  if ISOMORPH_TESTS_CANNOT_LOWER_VALUE_OF(set_of_points+candidate_point) {
   solve(set_of_points,candidate_point);
  }
 }
}
Invitado
fuente
1

tomando la notación de la respuesta anterior.

definamos primero la simetría propuesta por la función rotar (direction, number_of_time)

solución:

(1) cree hash de todo el conjunto de permutación con flag = 0 en cada uno. por ejemplo para n = 2, m = 2 000,001 = 0 000,010 = 0 000,011 = 0 ect '...

(2) comenzar desde el conjunto de inicio, por ejemplo i = 000,001

(3) gire el conjunto i en todas las direcciones utilizando la función de rotación (o cualquier otra simetría que desee), por ejemplo, la función de rotación se debe llamar 24 veces por cada permutación de la rotación.

ingrese la descripción de la imagen aquí

Explenation: cualquier número 1-6 puede estar frente a usted y cada uno de los números puede rotarse 4 veces, por lo tanto 6 * 4 = 24

(4) para cada conjunto recuperado de la combinación, establezca el indicador de hash en 1 (ya tiene un conjunto simétrico)

(5) actualice i al siguiente conjunto, por ejemplo i = 000,010

(6) si el conjunto i en el hash ya está marcado, vaya a (5) de lo contrario vaya a (3)

hemos terminado cuando todo el hash está marcado como 1.

pio
fuente
Me gusta mucho este enfoque, pero no sería tan útil para el problema original (¡no es que te haya dicho lo que era!). La razón es que esto todavía requiere la generación de cada conjunto de puntos, y el trabajo que tuve que hacer con cada conjunto fue muy pequeño, por lo que esto probablemente agregaría tanta sobrecarga como se ahorró. Sin embargo
rbennett485
1

Nota: Pienso solo en las simetrías espejo, no en las simetrías rotacionales aquí.

Supongamos que tenemos un (hiper) cubo de d dimensiones, cada una de n unidades de largo (un cubo de Rubik sería d = 3, n = 3 ).

Un algoritmo ingenuo generaría n ^ d combinaciones de puntos, y verificaría cada uno para un choque de simetría con todos los demás.

Si representamos una combinación de puntos como un vector de bits n ^ d bits de longitud, podemos usar un mapa (vector de bits -> booleano) para marcar todas las simetrías del vector de bits con verdadero . Entonces podríamos omitir una combinación si ya está marcada en el mapa.

Este enfoque es muy poco eficiente en cuanto al espacio: toma un mapa con 2 ^ (n ^ d) entradas, es decir, un mapa de bits con tantos bits. (Para el cubo de Rubik, sería 2 ^ 27 = 128Mbit = 16 Mbytes).

Solo podemos recordar las representaciones canónicas, es decir, aquellos vectores de bits que tienen el valor entero más pequeño, si se representan como una palabra sin signo de n ^ d -bit. Cuando generamos una nueva permutación de puntos, generamos todas sus simetrías, y solo verificamos si hemos visto la simetría con el valor numérico más pequeño. Esto nos permitirá almacenar un mapa con solo 2 ^ n bits (solo 1 byte para el cubo de Rubik), porque tenemos simetrías 2 ^ d . Sin embargo, nos hace generar estas simetrías 2 ^ d en cada paso, por lo que gastamos O (2 ^ (d ^ n + d)) = O (2 ^ (d ^ n) * 2 ^ d) tiempo. Aún pobre

Podemos aplicar la idea del párrafo anterior al caso unidimensional. Para generar todas las combinaciones en un vector de longitud d , podemos simplemente incrementar un número binario d bits de longitud, comenzando por todos los 0s. Dividamos nuestro vector en dos segmentos de d / 2 de longitud, por ejemplo, izquierda y derecha. Podemos notar que para cada 1bit en el segmento izquierdo solo necesitamos ver combinaciones que tengan 1bit en la posición simétrica de la sección derecha. De lo contrario, ya habríamos generado una combinación simétrica antes, cuando se intercambiaron las posiciones de los bits, y 0aparecieron antes de 1. De esta manera, para cada posición de bit en la mitad derecha (r) , y la posición simétrica en la mitad izquierda(l) solo necesitamos generar 3 combinaciones: (l = 0, r = 0); (l = 1, r = 1); (l = 1, r = 0) . Por lo tanto, solo necesitamos generar 2 ^ (d / 2) permutaciones de un vector de longitud d , produciendo 3 combinaciones para cada permutación.

Se puede construir un cubo de d dimensiones de n ^ (d-1) vectores. El truco anterior nos da los vectores más baratos que el enfoque ingenuo. Para generar un cubo, necesitamos O (n ^ (d-1) * 2 ^ (d / 2)) tiempo.

Si observamos el cubo a lo largo de la dimensión de nuestros vectores unidimensionales, podemos ver que no tenemos que verificar la simetría a lo largo de esta dimensión: al generar los cubos, eliminamos las simetrías para cada vector involucrado.

Ahora, si miramos a través de esta dimensión, podemos reutilizar el mismo truco.

Cuando miramos a través, miramos, por ejemplo, los primeros bits de vectores que forman un plano particular. Estos bits representan un vector de bits unidimensional. Podemos eliminar la mayoría de las combinaciones de sus bits por razones de simetría, como se describió anteriormente. Entonces, si elegimos un vector 1-d particular de un cubo (por ejemplo, el extremo superior izquierdo), podemos eliminar muchos vectores del mismo plano (por ejemplo, el superior) en función del valor de un bit particular. Entonces, para un vector en una posición simétrica espejo en el plano, podemos evitar generar todas las combinaciones que puedan tener ese bit establecido (o no establecido), reduciendo drásticamente el número de vectores que tenemos que generar para un plano particular. Cada bit eliminado reduce a la mitad el número de posibles vectores en la posición reflejada en el espejo. Esto nos da una secuencia de planos sin contrapartidas simétricas a lo largo de cada dimensión.

Este truco se puede aplicar para limitar aún más la generación de permutaciones de los siguientes planos a lo largo de una tercera dimensión, etc.

Aunque no es un algoritmo completo, espero que esto ayude.

9000
fuente