Algorithme pour générer tous les ensembles de m points dans un réseau cubique nxnxn qui sont uniques sous symétrie


10

J'implémente un algorithme qui va être assez complexe sur le plan informatique et je veux essayer de m'assurer que je ne fais pas de travail inutile.

Il existe un réseau cubique nxnxn, par exemple si n = 2, il se compose de (0,0,0), (0,1,0), (1,0,0), (1,1,0), (0, 1,1), (0,0,1), (1,0,1), (1,1,1).

À partir de ce réseau, je générerai récursivement tous les ensembles de m points, quelque chose comme:

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);
}

Cela peut ensuite être appelé en commençant par un set_of_points vide.

La nature du problème est telle que je n'ai pas réellement besoin de chaque permutation de m points, juste ceux qui sont uniques sous les symétries naturelles du cube.

Par exemple, prenez un cube 2x2x2 et supposons que nous voulons tous les ensembles de 1 point. Sous l'algorithme de base ci-dessus, il existe 8 ensembles différents de 1 point.

Cependant, en utilisant les symétries du cube, nous pouvons réduire cela à 1 ensemble unique de 1 points, car tous les 8 originaux sont équivalents sous les symétries du cube (ils sont tous des `` coins '' dans ce cas).

Si le cube est 2x2x2 et m = 2, l'algorithme de base contient 28 ensembles, mais cela se réduit à seulement 3 sous symétrie (par exemple {(0,0,0), (1,0,0)}, {(0 , 0,0), (1,1,0)}, {(0,0,0), (1,1,1)})

Évidemment, le calcul sur 3 ensembles de points est bien meilleur que 28, alors ma question est de savoir comment ne pas générer d'ensembles de points qui sont symétriquement équivalents à un ensemble déjà généré? Ou si cela n'est pas possible, comment puis-je au moins réduire un peu le nombre de jeux.

(Remarque - si m = 1, cela est relativement facile - il suffit de choisir les points qui sont plus proches de (0,0,0) que tous les autres sommets, avec un peu de fudging aux limites. C'est pour m> 1 que cela devient être un vrai problème)


1
Par équivalence symétrique, vous incluez quelles opérations: plans symétriques passant par le centre? Inversion ponctuelle par le centre? Les trois axes de rotation à 4 par le centre?
BmyGuest

N'importe quelle isométrie ferait l'affaire
rbennett485

Si vous êtes toujours là, la répétition serait-elle autorisée dans le m-set de points? Par exemple, pour m = 3, {(0,0,0), (1,1,1), (0,0,0)} est-il considéré comme une sélection valide?
blackpen

@blackpen non, il faut que ce soit 3 points uniques
rbennett485

Réponses:


1

Concept de base:

(1) Nous pouvons voir le point (0,0,0) simplement comme 000. Chaque point du réseau tombe maintenant dans une séquence simple. Le premier point est 000, puis 001, puis 010 011 100 101 110 et 111. Il s'agit de l'ordre dans lequel vous allez essayer de les ajouter à l'ensemble de points.

(2) De même, l'ensemble {(0,0,0), (0,0,1), (0,1,0)} peut simplement être considéré comme 000001010, et l'ensemble {(0,0,0) , (0,1,0), (0,0,1)} peut simplement être considéré comme 000010001. Deux ensembles différents ne peuvent pas avoir la même séquence, et vous pouvez facilement voir 000001010 comme étant numériquement ou alphabétiquement inférieur à 000010001. Appelons cela la valeur de consigne. Chaque ensemble possible de N points a maintenant une valeur de consigne, et tous les ensembles possibles de N points appartiennent désormais à une simple liste bien ordonnée.

(3) Chaque groupe isomorphe d'ensembles de points a exactement un membre qui aura la valeur d'ensemble la plus basse. Ce sont les seuls où nous effectuons réellement des "calculs utiles".

(4) Voici la partie qui nécessitera un travail important. Avant d'exécuter résoudre (set_of_points + new_point), vous voulez voir si un isomorphisme abaisserait la valeur de consigne pour set_of_points + new_point. Si un isomorphisme abaisserait la valeur définie, il ne s'agit PAS d'un membre de la valeur la plus basse de l'ensemble isomorphe. Nous sautons tout travail sur ce new_point. Nous sautons également tout le travail récursif que nous aurions fait à l'intérieur de cette résolution (set_of_points, candidate_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);
  }
 }
}

1

en prenant la notation de la réponse ci-dessus.

permet d'abord de définir la symétrie proposée par la fonction rotation (direction, number_of_time)

Solution:

(1) créer un hachage de tous les ensembles de permutation avec flag = 0 sur chacun. par exemple pour n = 2, m = 2 000 001 = 0 000 010 = 0 000 011 = 0 ect '...

(2) commencer à partir de l'ensemble init, par exemple i = 000,001

(3) faites pivoter l'ensemble i dans toutes les directions en utilisant la fonction rotation (ou toute autre symétrie que vous aimez) par exemple, la fonction rotation doit être appelée 24 fois pour chaque permutation de la rotation.

entrez la description de l'image ici

explication: n'importe quel nombre 1-6 peut être devant vous et chacun peut être tourné 4 fois, donc 6 * 4 = 24

(4) pour chaque ensemble récupéré de la combinaison, définissez le drapeau de hachage sur 1 (il a déjà un ensemble symétrique)

(5) mettre à jour i à l'ensemble suivant, par exemple i = 000,010

(6) si l'ensemble i dans le hachage est déjà marqué, passez à (5) sinon passez à (3)

nous avons terminé lorsque tout le hachage est marqué comme 1.


J'aime bien cette approche, mais ce ne serait pas du tout utile pour le problème d'origine (pas que je vous ai dit ce que c'était!). La raison étant que cela nécessite toujours la génération de chaque ensemble de points, et le travail que j'avais à faire avec chaque ensemble était très petit, ce qui ajouterait probablement autant de frais généraux qu'il a économisé. Pour les applications avec beaucoup de calculs à faire pour chaque ensemble, ce serait bien pratique
rbennett485

1

Remarque: je ne pense qu'aux symétries de miroir, pas aux symétries de rotation ici.

Supposons que nous ayons un (hyper) cube de d dimensions, chacune de n unités de long (un cube de Rubik serait d = 3, n = 3 ).

Un algorithme naïf générerait n ^ d combinaisons de points et vérifierait chacun un conflit de symétrie avec tous les autres.

Si nous représentons une combinaison de points comme un vecteur de bits de n ^ d bits de long, nous pouvons utiliser une carte (vecteur de bits -> booléen) pour marquer toutes les symétries du vecteur de bits avec true . Nous pourrions alors ignorer une combinaison si elle est déjà indiquée sur la carte.

Cette approche est très peu efficace en espace: elle prend une carte avec 2 ^ (n ^ d) entrées, c'est-à-dire une bitmap avec autant de bits. (Pour le cube de Rubik, ce serait 2 ^ 27 = 128Mbit = 16 Mo.)

Nous ne pouvons nous souvenir que des représentations canoniques, c'est-à-dire de tels vecteurs de bits qui ont la plus petite valeur entière, s'ils sont représentés comme un mot non signé à n ^ d bits. Lorsque nous générons une nouvelle permutation de points, nous générons toutes ses symétries, et vérifions uniquement si nous avons vu la symétrie avec la plus petite valeur numérique. Cela nous permettra de stocker une carte avec seulement 2 ^ n bits (seulement 1 octet pour le cube de Rubik), car nous avons 2 ^ d symétries. Cela nous fait cependant générer ces 2 ^ d symétries à chaque étape, donc nous passons O (2 ^ (d ^ n + d)) = O (2 ^ (d ^ n) * 2 ^ d) temps. Toujours pauvre.

Nous pouvons appliquer l'idée du paragraphe précédent au cas unidimensionnel. Pour générer toutes les combinaisons dans un vecteur de longueur d , nous pouvons simplement incrémenter un nombre binaire d bits de long, à partir de tous les 0s. Divisons notre vecteur en deux segments d / 2 , par exemple gauche et droite. Nous pouvons remarquer que pour chaque 1bit dans le segment de gauche, nous avons seulement besoin de voir les combinaisons qui ont un 1bit dans la position symétrique de la section de droite. Sinon, nous aurions déjà généré une combinaison symétrique plus tôt, lorsque les positions des bits ont été permutées, et le 0sont arrivés avant le 1. De cette façon, pour chaque position de bit dans la moitié droite (r) et la position symétrique dans la moitié gauche(l) il suffit de générer 3 combinaisons: (l = 0, r = 0); (l = 1, r = 1); (l = 1, r = 0) . Il suffit donc de générer 2 ^ (d / 2) permutations d'un vecteur de longueur d , ce qui donne 3 combinaisons pour chaque permutation.

Un cube de d dimensions peut être constitué de n ^ (d-1) vecteurs. L'astuce ci-dessus nous donne les vecteurs moins chers que l'approche naïve. Pour générer un cube, nous avons besoin d'un temps O (n ^ (d-1) * 2 ^ (d / 2)) .

Si nous regardons le cube le long de la dimension de nos vecteurs à 1 dimension, nous pouvons voir que nous n'avons pas à vérifier la symétrie le long de cette dimension: lors de la génération des cubes, nous éliminons les symétries pour chaque vecteur impliqué.

Maintenant, si nous regardons à travers cette dimension, nous pouvons réutiliser la même astuce.

Lorsque nous regardons à travers, nous regardons par exemple les premiers bits de vecteurs faisant un plan particulier. Ces bits représentent un vecteur de bits à une dimension. Nous pouvons éliminer la plupart des combinaisons de ses bits pour des raisons de symétrie, comme décrit ci-dessus. Donc, si nous choisissons un vecteur 1-d particulier d'un cube (par exemple le plus à gauche en haut), nous pouvons éliminer de nombreux vecteurs du même plan (par exemple au sommet) en fonction de la valeur d'un bit particulier. Ainsi, pour un vecteur dans une position symétrique par rapport au miroir sur le plan, nous pouvons éviter de générer toutes les combinaisons pouvant avoir ce bit défini (ou non), réduisant ainsi le nombre de vecteurs que nous devons générer pour un plan particulier de manière drastique. Chaque bit éliminé divise par deux le nombre de vecteurs possibles à la position réfléchie par le miroir. Cela nous donne une séquence de plans sans homologues symétriques le long de chaque dimension.

Cette astuce peut être appliquée pour limiter davantage la génération de permutations des plans suivants le long d'une troisième dimension, etc.

Bien qu'il ne s'agisse pas d'un algorithme complet, j'espère que cela vous aidera.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.