Arrays, rotations, beer coasters, and symmetries of a hypercube Bradley Lucier 04 May 2023 22:57 UTC

TL;DR: You can generate every "symmetry" of an array by combining one
permutation and one reversal.

I defined a little array with a big F in the middle:

(define A
   (list*->array 2 '((_ F F F _)
                     (_ F _ _ _)
                     (_ F F F _)
                     (_ F _ _ _)
                     (_ F _ _ _))))

and thought about the nontrivial permutations and reversals of A (using
array->list* and Gambit's pretty-print):

(array-permute A '#(1 0))
((_ _ _ _ _)
  (F F F F F)
  (F _ F _ _)
  (F _ F _ _)
  (_ _ _ _ _))
(array-reverse A '#(#f #t))
((_ F F F _)
  (_ _ _ F _)
  (_ F F F _)
  (_ _ _ F _)
  (_ _ _ F _))
(array-reverse A '#(#t #f))
((_ F _ _ _)
  (_ F _ _ _)
  (_ F F F _)
  (_ F _ _ _)
  (_ F F F _))
(array-reverse A '#(#t #t))
((_ _ _ F _)
  (_ _ _ F _)
  (_ F F F _)
  (_ _ _ F _)
  (_ F F F _))

All but the last have the big F pointing the wrong way, like the array
was a coaster under a drink and it's been flipped over.  The last one
has rotated the original array by 180 degrees, *without* flipping over
the coaster.

I wondered whether you could combine a permutation and a reversal to get
a counterclockwise rotation by 90 degrees, and it's easy to see that

(array-reverse (array-permute A '#(1 0)) '#(#t #f))
((_ _ _ _ _)
  (F _ F _ _)
  (F _ F _ _)
  (F F F F F)
  (_ _ _ _ _))

So I generated all combinations of array-permute and array-reverse (even
the trivial ones that don't modify the array):

(array-reverse (array-permute A '#(0 1)) '#(#f #f))
((_ F F F _)
  (_ F _ _ _)
  (_ F F F _)
  (_ F _ _ _)
  (_ F _ _ _))
(array-reverse (array-permute A '#(0 1)) '#(#f #t))
((_ F F F _)
  (_ _ _ F _)
  (_ F F F _)
  (_ _ _ F _)
  (_ _ _ F _))
(array-reverse (array-permute A '#(0 1)) '#(#t #f))
((_ F _ _ _)
  (_ F _ _ _)
  (_ F F F _)
  (_ F _ _ _)
  (_ F F F _))
(array-reverse (array-permute A '#(0 1)) '#(#t #t))
((_ _ _ F _)
  (_ _ _ F _)
  (_ F F F _)
  (_ _ _ F _)
  (_ F F F _))
(array-reverse (array-permute A '#(1 0)) '#(#f #f))
((_ _ _ _ _)
  (F F F F F)
  (F _ F _ _)
  (F _ F _ _)
  (_ _ _ _ _))
(array-reverse (array-permute A '#(1 0)) '#(#f #t))
((_ _ _ _ _)
  (F F F F F)
  (_ _ F _ F)
  (_ _ F _ F)
  (_ _ _ _ _))
(array-reverse (array-permute A '#(1 0)) '#(#t #f))
((_ _ _ _ _)
  (F _ F _ _)
  (F _ F _ _)
  (F F F F F)
  (_ _ _ _ _))
(array-reverse (array-permute A '#(1 0)) '#(#t #t))
((_ _ _ _ _)
  (_ _ F _ F)
  (_ _ F _ F)
  (F F F F F)
  (_ _ _ _ _))

These 8 combinations give all orientations of the coaster:  The four
rotations without flipping it over, and then the four rotations after
flipping it over.

In fact, combining array-reverse and array-permute is sufficient to
generate all $d!\times 2^d$ symmetries of a $d$-dimensional array:

https://en.wikipedia.org/wiki/Hyperoctahedral_group

And it's been nearly 50 years since I studied undergraduate group
theory, so no, I don't understand the Wikipedia page either.

Still. it's easy to see that combining array-permute and array-reverse
generates the correct number of distinct arrays:

There are $d!$ permutations, and they're all unique.  And after each
permutation, one can choose to reverse (or not) each of the $d$ axes, so
that gives $2^d$ combinations of reversals for each permutation, and
*they're* all unique.  So overall we have $d!\times 2^d$ unique
symmetries, which is all of them.

One can ask whether separating permutations from reversals is a good
design in this SRFI.  I think it is, often one wants to do one without
the other, and it takes just two calls to get a general transform.

Code is at the end.

Brad

(import (srfi 231))

(define A
   (list*->array 2 '((_ F F F _)
                     (_ F _ _ _)
                     (_ F F F _)
                     (_ F _ _ _)
                     (_ F _ _ _))))

(pretty-print '(array-permute A '#(1 0)))
(pretty-print (array->list*
                (array-permute A '#(1 0))))

(for-each (lambda (flip?)
             (pretty-print
              `(array-reverse A ',flip?))
             (pretty-print
              (array->list*
               (array-reverse A flip?))))
           '(#(#f #t)
             #(#t #f)
             #(#t #t)))

(for-each (lambda (permutation)
             (for-each (lambda (flip?)
                         (pretty-print
                          `(array-reverse
                            (array-permute A ',permutation)
                            ',flip?))
                         (pretty-print
                          (array->list*
                           (array-reverse
                            (array-permute A permutation)
                            flip?))))
                       '(#(#f #f)
                         #(#f #t)
                         #(#t #f)
                         #(#t #t))))
           '(#(0 1)
             #(1 0)))