Ruby, 108 105 101 97 bytes
1.upto(4e3){|i|a=[]
24.times{|j|i%2>0&&a<<j%i^=(643-j%2*504)*83906560;i>>=j==11?13:1}
a[8]||p(a)}
Try it online!
Test version
Try it online!
Explanation (updated to match latest version)
The (Extended) Binary Golay Code is an error checking code which converts 12 data bits into 12 data and 12 check bits. It is discussed on Wikipedia but Youtube mathematician Another Roof has a great video on it as well as several others related to this Steiner system. There is also a "Perfect" Binary Golay Code which is the same but with one check bit deleted. Each of the 12 input bits generates a 24-bit basis codeword, and the basis codewords for all 1
's in the data bits are then XORed together to give the encoded output.
There are 759 possible codewords with 8 1
bits, corresponding to the required Steiner system. There are also 759 possible codewords with 16 1
bits, and one each with 0 and 24 1
bits. The remaining 4096-(759+1)*2=2576 codewords have 12 1
bits.
The presentation of the basis codewords used in the above sources is given below (I have flipped it horizontally so that bit 0 is at the right.)
The data bits are just a copy of the input. The check bits of the n
th basis codeword are defined as follows: Each bit is assigned to one vertex of an icosahedron (or alternatively, one face of a dodecahedron.) The 5 bits that are adjacent to the n
th bit are defined as 0
while the other 7 (including the n
th bit itself) are defined as 1.
This labelling from the above mentioned sources considers two polar faces 10
and 11
while all remaining faces 0-9
are equatorial. This gives a simple, repetitive matrix for bits 0-9
but the last two columns/rows for bits 10-11
are different, which makes golfing awkward.
Check bits Data bits Unfolded dodecahedron
9 6 3 0 9 6 3 0 *
10 0011111001 000000000001 / \
01 0111110010 000000000010 * *
10 1111100100 000000000100 |10 |
01 1111001001 000000001000 *---*---*---*---*---*
10 1110010011 000000010000 | 8 | 6 | 4 | 2 | 0 |
01 1100100111 000000100000 * * * * * *
10 1001001111 000001000000 / \ / \ / \ / \ / \ /
01 0010011111 000010000000 * * * * * *
10 0100111110 000100000000 | 9 | 7 | 5 | 3 | 1 !
01_1001111100_001000000000 *---*---*---*---*---*
11 1010101010 010000000000 |11 |
11 0101010101 100000000000 * *
\ /
*
This answer therefore uses an alternative labelling of the faces of the dodecahedron as shown below. In this there are 6 equatorial and 6 polar faces. The adjacent faces for each face can therefore be given simply as follows:
ADJACENT FACES (consider MOD 12)
Equatorial face (even n) n-2, n-1, n+1, n+2, n+3
Polar face (odd n) n-4, n-3, n-1, n+2, n+4
BITS PER ALGORITHM
Check bits Data bits Unfolded Dodecahedron
9 0 3 6 3 6 9 0 * * *
110010001111 000000000001 / \ / \ / \
010101101110 000000000010 * * * * * * * * *
001000111111 000000000100 |11 |/ \| 7 |/ \| 3 |/ \
010110111001 000000001000 *---* *---* *---* *
100011111100 000000010000 |10 | 8 | 6 | 4 | 2 | 0 |
011011100101 000000100000 * *---* *---* *---*
001111110010 000001000000 \ /| 9 |\ /| 5 |\ /| 1 |
101110010101 000010000000 * * * * * * * * *
111111001000 000100000000 \ / \ / \ /
111001010110 001000000000 * * *
111100100011 010000000000
100101011011 100000000000
2 1 1 1 BITS AS IMPLEMENTED
1 8 5 2 9 6 3 0 IN PROGRAM
Because we are only interested in the number of 1
bits in the codeword, some scrambling of the columns in the table is OK to give a shorter solution, as noted above. The above mentioned adjacencies are therefore shifted over by 7 to avoid negative numbers (and for golfing reasons). They are then flipped because this gives multiples of 5, which is handy for golfing.
Adjacency shifted left 7 Adjacency shifted left 7 & flipped
Equatorial 100010011111 = 2207 110010001111 = 3215 = 643*5
Polar 011010100111 = 1703 001010110111 = 695 = 139*5
We iterate through all 4096 posssible 12-bit words (actually only the first 3986
are needed, so we stop at 4000 for golfing reasons.) If the Golay code has 8 1
bits (coded as less than 9 1
bits) we output a row of the Steiner system (for this reason we need to start iterating at 1 to suppress the outputting of the empty set for iteration 0.) The row of the Steiner system is generated as shown in the commented code.
Commented code
The value of i
initially corresponds to the 12 data bits for the current iteration.
The inner j
loop continually shifts the value of i
right to extract the least significant bit, but if we imagine i
was static (not shifted right) we see that the check bits are composed into bits 24-35 as follows:
3 3 3 2 2 2 1 1 1
6 3 0 7 4 1 8 5 2 9 6 3 0
000000000000 000000000000 000000000000 DDDDDDDDDDDD
........ CCCCCCCCCCCC
........ CCCCCCCCCCCC
The data bits start in bits 0-11. To get the check bits, the numbers 643 (equatorial) or 139 (polar) are simultaneously multiplied by 5 to give 3215
or 695
as mentioned above, and by (1<<24)+(1<<12)=16781312
. This gives 2 copies of the check bits. 2 copies of the check bits are therefore composed into bits 24-35 and bits 12-23 respectively. Some high order bits spill over beyond 35 and are lost, but the same bits spill over beyond bits 12-23 into bit 24 onwards, so that wraparound is achieved and a correct copy of the final check code is obtained in bits 24-35 (with garbage in bits 12-23 and bits 36 onwards.)
1.upto(4e3){|i| #Iterate through all 4096 12-bit numbers (actually we start at 1 and stop at 4000)
a=[] #Empty array to hold the current row
24.times{|j| #Iterate through 24 bits of output
i%2>0&&a<<j% #If bit j is 1, append number j to array a (expression on next line is bigger than j so modulo linker % doesn't change j)
i^=(643-j%2*504)*83906560 #Take 643 for equatorial, 643-504=139 for polar. Multiply by 5*(1<<24|1<<12)=83906560 to give 2 copies of the adjacencies
#The check bits are saved in bits 12-23 and 24-35. 2 copies are used to ensure proper overlap in bits 24-35
i>>=j==11?13:1} #Shift i right 1 bit. If all 12 data bits are done (j=11), jump an additional 12 to bit 24 to do check bits
a[8]||q<<a} #if the j loop has added less than 9 entries to a (element with index 8 does not exist) output a row of the Steiner system
The first expression below is used to append the current value of j
to array a
and modify the check bits in i
when the data bit being extracted by i%2
is a 1
. It is functionally equivalent to the second expression. Since j
is always in the range 0..24
and i
(once xored with check bits) is guaranteed to be huge, the modulo operator %
does not change j
and saves the 2 bytes needed for brackets if ;
was used.
The same i%2&&a<<j
is also used to append the check bits to a
. When this is happening the latter part of the expression continues to modify the high order bits of i
with nonsense information. This does not matter, as it never affects the output.
used in code i%2>0 && a<<j % i^=(643-j%2*504)*83906560
equivalent i%2>0 && (a<<j ; i^=(643-j%2*504)*83906560)
Categorization of data bit configurations that produce Steiner system elements
The approach in this answer takes advantage of the 6 fold symmetry of the dodecahedron rotated 60 degrees about a z
axis passing through 2 opposite vertices and simultaneously flipped in the xy
plane. The code in the link below outputs (in base 4) the canonical data-bit configurations (of a given Hamming weight h
) that lead to outputs of rows of the Steiner system.
Try it online!
The output of the code (manually collated) is given below for interest. There are 124 canonical data-bit configurations with no rotational symmetry, which produce 6x124=744 data-bit configurations, and 5 canonical data-bit configurations with 2-fold rotational symmetry, which produce the remaining 15 data-bit configurations. Many occur in mirror image pairs. Further comment may be added soon.
Canonical data-bit configurations giving rise to rows of the Steiner system (rotate by 1,3,5,7,9 and 11 bits to get the full 759 possibilities)
h=1 ( 2*6=12) "1" "2"
h=2 (10*6=60) "3" "11" "12" "22" "101" "102" "202" "1002"
"21" "201"
h=3 (30*6=180) "13" "103" "111" "112" "122" "203" "212" "222" "1003" "1011" "1012" "1021" "1022" "1202" "2003" "2012" "2022" "10102"
"31" "301" "211" "221" "302" "1101" "2101" "1201" "2201" "2021" "2102" "2202"
h=4 (40*6=240)"33" "113" "123" "213" "232" "303" "1023" "1103" "1121" "1222" "2013" "2023" "2032" "10111" "10121" "10122" "10212" "11012" "11022" "11102" "11202" "12021" "12102" "12202" "20203" "20222"
"311" "321" "312" "3201" "3011" "1211" "2221" "3102" "3202" "2302" "10221" "11021" "20211" "20221"
h=4 (5*3=15) "3003" "12012" "11011" "22022" (these have 2-fold rotational symmetry)
"21021"
h=5 (30*6=180) "1033" "1132" "1231" "1312" "1322" "2123" "2232" "10131" "10223" "10303" "11112" "11203" "12103" "13022" "21122" "21203" "21212" "22203"
"3301" "2311" "1321" "2131" "2231" "3212" "2322" "10322" "21111" "21103" "22031" "22112"
h=6 (10*6=60) "3113" "3223" "11311" "12132" "12213" "13031" "23032" "122222"
"23121" "31221"
h=7 ( 2*6=12) "20333" "111232"
AssertionError
if the output is incorrect. The boilerplate I used assumes input with a Pythonic list/tuple on each line. It could be modified to take an array or another format. \$\endgroup\$AssertionError
/ exit code 1 if incorrect. 97 byte version coming tomorrow. BTW thanks aeh5040 for the OP. This has been a fun one. \$\endgroup\$