Graphical Supersymmetry Algebras
Thanks to Dr. James Hughes, Matthew Lynn, Chris Walker, Chas Leichner, Alex Ray, Alex Zhu, Dr. Cornelia Van Cott, Paul Sebexen, Colin Aitken, Chuck Moulton, Sebastien Zany, and Nick for joining me in playing with this problem.
What is an adinkra?
Adinkras are colored connected simple graphs, that are bipartite and nregular.
Adinkras are a graphical representation of supersymmetry algebras.
The prefix super comes from the theory of supersymmetry in theoretical physics. Superalgebras and their representations, supermodules, provide an algebraic framework for formulating supersymmetry.
A superalgebra is a (\mathbb{Z}_2)graded algebra. In other words, it is “an algebra over a commutative ring or field with decomposition into ‘even’ or ‘odd’ pieces and a multiplication operator that respects the grading.”
What is an odd adinkra?
An adinkra is odd if the sum of every 4cycle in the adinkra is odd. To create an odd adinkra: we assign 0 or 1 to every edge of an ndimensional cube such that, for each face of the cube, the sum of the edges of the face is odd.
How many unique ndimensional odd adinkras exist?
Before we attack this, let’s elaborate on the problem:
First some notation must be fixed. By (E(G)), we mean the edges of the graph (G), and by (Q_n), we mean the graph corresponding to the (n)dimensional cube. The formal statement is then as follows.
How many edge 2colourings (\phi : E(Q_n) to {0,1}) exist such that, for each 4cycle (C) of (Q_n), the sum of the edge colours is odd: (\sum_{e \in E(C)} \phi(e) = 1) mod (2) ?
Note that we bastardize the term “edge ncolourings” (\equiv) assignment of (x \in {0,1,..,n1}) to every edge.
Breaking Down The Problem: The Overture
Let (e_n) denote the number of edge colourings of (Q_n) for which the sum of the edge colours is even, and let (o_n) denote the number of edge colourings for which the sum is odd. As (Q_0) contains a single vertex and no edges, there are no edge colourings with either property, so we set (e_0 = o_0 = 1).
Now (Q_1) contains two vertices with a single edge between them, so (e_1 = o_1 = 1). The slightly more interesting graph (Q_2) corresponds to a square, which can have its edges 2coloured in (2^4) ways. Half of which will correspond to an even sum, the other half to an odd, so (e_2 = o_2 = 8). The graph (Q_3) can be 2coloured in (2^{12}) ways, half of which must be even and the other half odd, so (e_3 = o_3 = 2^{11}).
Conjecture: For all (n), (e_n = o_n = 2^{  E(Q_n)   1}). 
When constructing (Q_{n+1}) from (Q_n) we are duplicating the graph (Q_n) and connecting every vertex of the duplicate to the original.
Combinatorial Approach
For a 2cube: we have 2 different states [the sides sum to 1 or 3], and 4 unique rotational variants of each state. (2*4 =8)
Can we write this in a closed form? The set of all unique states of the cube is (2^8).
We have 8 different choosing points, one for each of the 8 edges. For each of these choosing points, we have 2 different options: pick 0 or 1.
What about for higher dimensions?
For the front face of the cube, we have (2^{2^{n1}1})
For the back face of the cube, we also have (2^{2^{n1}1})
The state of this edge is completely determined by this edge [following an intuition we will confirm in the following section]. Therefore, instead of adding 4 more options, we only add 2.
This gives us (2^{2^{(n−1)}} \cdot 2^{2^{(n+1)}} \cdot 2^1 = 2^{2^n1}). (_\blacksquare)
Visually Formalizing our Intuition
Until we prove our hunch is correct, there is a hole in the induction step where one would need to show that many ways to glue to n1 dimensional hypercubes together exist.
Intuitively, as soon as you fill in 1 edge (0 or 1) that fixes the rest of them (\rightarrow) meaning that there are only 2 valid ways to connect any 2 (n)dimensional cubes into a ((n+1))dimensional cube.
This can be visually formalized and inductively proven:
We can add the corresponding edges of the (n1)cubes to get a new (n1) cube, and then the old problem of coloring the edges between the two cubes becomes the problem of coloring vertices so that the sum of the colors of two vertices is the same as the number on the edge between them.
Computational Attacks
All code used in this post is on my github.
The settheoretic approach is just a cool way to think about graph generation.
# Hypercube Problem
from itertools import chain, combinations
# Create generator set for hypercube of dimension x
def S(x):
return set(range(0,x))
# From http://stackoverflow.com/questions/18035595/powersetsinpythonusingitertools
def powerset(iterable):
s = list(iterable)
return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))
# Generate a list of edges given a list of vertices
def edges(verts):
return set(frozenset([x, y]) for x in verts for y in verts if len(x ^ y) == 1)
# Run this to generate hypercube of specified number of dimensions
def Qgen(dimensions):
genSet = S(dimensions)
print(genSet)
Verts = list(powerset(genSet))
print(Verts)
sVerts = set(frozenset(x) for x in Verts)
print(sVerts)
print(len(sVerts))
Edges = edges(sVerts)
print(Edges)
print(len(Edges))
return
Shiny Graphviz to generate ndimensional Hamming cubes which falls apart around n=8.
import colorsys
import itertools
import sys
n = int(sys.argv[1])
bits = list(itertools.product([0, 1], repeat=n))
HSV_tuples = [(x*1.0/n, 0.5, 0.5) for x in range(n+1)]
tuples = map(lambda x: '#%02x%02x%02x' % tuple(map(lambda x: int(x*255), colorsys.hsv_to_rgb(*x))), HSV_tuples)
def diff(a, b):
difference = 0
for a0, b0 in zip(a, b):
difference += a0 != b0
return difference
def reprtag(t):
return "".join(str(b) for b in t)
print 'graph cube {'
print ' ranksep=', n/2.0, ';'
print ' fontsize=8;'
seen = set()
for ((a, b), c) in zip(itertools.product(bits, bits), itertools.cycle(tuples)):
if diff(a, b) == 1 and (b, a) not in seen:
seen.add((a, b))
print ' "', reprtag(a), '"', '', '"', reprtag(b), '" [color="', c, '"]'
print '}'
Dependency:
sudo aptget install graphviz
Make them all in one go:
for i in $(seq 1 8); do python cube.py $i > cube${i}.dot;
This program generates text files containing all unique ncubes satisfying the properties described. This works in an arbitrary number of dimensions and takes a file of the previous dimension as input.
import string
dimension_start = 3
dimension_end = 4
# loop through the dimensions you care about
# (stored in files, so you don't have to start from scratch each time)
for dimension in range( dimension_start, dimension_end + 1 ):
# initialize lists & dictionaries as empty
vertices = []
edges = []
faces = {}
# create a list of vertices in string binary form (e.g., '1001' = 9 ).
for vertex_int in range( 2**dimension ):
vertices.append( bin( vertex_int )[2:].zfill( dimension ) )
# create a list of valid edges in string binary form (e.g., '10011011' = 9,11 ).
for vertex in vertices:
for i in range( len( vertex ) ):
if vertex[i:i+1] == '0':
edges.append( vertex + vertex[:i] + '1' + vertex[i+1:] )
# create a list of valid edges in string binary form (e.g., '10011011' = 9,11 ).
# create a list of valid faces as a lookup table of edges.
for vertex in vertices:
for i in range( len( vertex ) ):
if vertex[i:i+1] == '0':
edges.append( vertex + vertex[:i] + '1' + vertex[i+1:] )
# create a list of valid faces as a lookup table of edges.
for j in range( i + 1, len( vertex ) ):
if not ( vertex + vertex[:i] + '1' + vertex[i+1:] in faces ):
faces[ vertex + vertex[:i] + '1' + vertex[i+1:] ] =
[ vertex + vertex[:i] + '1' + vertex[i+1:],
vertex + vertex[:j] + '1' + vertex[j+1:],
vertex[:i] + '1' + vertex[i+1:] + vertex[:i] + '1' + vertex[i+1:j] + '1' + vertex[j+1:],
vertex[:j] + '1' + vertex[j+1:] + vertex[:i] + '1' + vertex[i+1:j] + '1' + vertex[j+1:] ]
if not ( vertex + vertex[:j] + '1' + vertex[j+1:] in faces ):
faces[ vertex + vertex[:j] + '1' + vertex[j+1:] ] =
[ vertex + vertex[:i] + '1' + vertex[i+1:],
vertex + vertex[:j] + '1' + vertex[j+1:],
vertex[:i] + '1' + vertex[i+1:] + vertex[:i] + '1' + vertex[i+1:j] + '1' + vertex[j+1:],
vertex[:j] + '1' + vertex[j+1:] + vertex[:i] + '1' + vertex[i+1:j] + '1' + vertex[j+1:] ]
if not ( vertex[:i] + '1' + vertex[i+1:] + vertex[:i] + '1' + vertex[i+1:j] + '1' + vertex[j+1:] in faces ):
faces[ vertex[:i] + '1' + vertex[i+1:] + vertex[:i] + '1' + vertex[i+1:j] + '1' + vertex[j+1:] ] =
[ vertex + vertex[:i] + '1' + vertex[i+1:],
vertex + vertex[:j] + '1' + vertex[j+1:],
vertex[:i] + '1' + vertex[i+1:] + vertex[:i] + '1' + vertex[i+1:j] + '1' + vertex[j+1:],
vertex[:j] + '1' + vertex[j+1:] + vertex[:i] + '1' + vertex[i+1:j] + '1' + vertex[j+1:] ]
if not ( vertex[:j] + '1' + vertex[j+1:] + vertex[:i] + '1' + vertex[i+1:j] + '1' + vertex[j+1:] in faces ):
faces[ vertex[:j] + '1' + vertex[j+1:] + vertex[:i] + '1' + vertex[i+1:j] + '1' + vertex[j+1:] ] =
[ vertex + vertex[:i] + '1' + vertex[i+1:],
vertex + vertex[:j] + '1' + vertex[j+1:],
vertex[:i] + '1' + vertex[i+1:] + vertex[:i] + '1' + vertex[i+1:j] + '1' + vertex[j+1:],
vertex[:j] + '1' + vertex[j+1:] + vertex[:i] + '1' + vertex[i+1:j] + '1' + vertex[j+1:] ]
# read in the n1 dimension valid cubes
cube_small_file = open( 'cube' + str( dimension  1 ) + '.txt', 'r' )
cubes_small = []
for line in cube_small_file:
cubes_small.append( line.strip( 'n' ) )
cube_small_file.close()
# open the n dimension cube file for writing
cube_big_file = open( 'cube' + str( dimension ) + '.txt', 'w' )
# open an error file for writing
cube_error_file = open( 'cube_error.txt', 'w' )
# combine every cube with every other cube
for cube1_string in cubes_small:
for cube2_string in cubes_small:
# read in n1 dimension cubes
cube1_list = [ '0' + cube1[:dimension1] + '0' + cube1[dimension1:] for cube1 in cube1_string.split( ',' ) ]
cube2_list = [ '1' + cube2[:dimension1] + '1' + cube2[dimension1:] for cube2 in cube2_string.split( ',' ) ]
# combine cubes the two possible ways
for binary_value in range( 0, 2 ):
new_edges = []
new_edges_track = []
new_edges_face_order = []
cube_big_edges = {}
# add the edges of two n1 dimension cubes to the edge list for the n cube
for cube1_edge in cube1_list:
cube_big_edges[ cube1_edge[:1] ] = cube1_edge[1:]
for cube2_edge in cube2_list:
cube_big_edges[ cube2_edge[:1] ] = cube2_edge[1:]
# create a list of the new edges
for edge in edges:
if not ( edge in cube_big_edges ):
new_edges.append( edge )
# initialize one new edge (to 0 or 1 depending on the for loop)
for edge in new_edges:
cube_big_edges[ edge ] = str( binary_value )
break
# order the list of new edges so that only one edge will be unknown at any time
new_edges_track = list( new_edges )
for edge in new_edges:
face = faces[ edge ]
for face_edge in face:
if face_edge in new_edges_track:
new_edges_track.remove( face_edge )
new_edges_face_order.append( face_edge )
# go through the edges in that order, filling in new edges each time
for edge in new_edges_face_order:
odd_even_total = 0
face = faces[ edge ]
for face_edge in face:
if face_edge in cube_big_edges:
odd_even_total = odd_even_total + int( cube_big_edges[ face_edge ] )
for face_edge in face:
if not ( face_edge in cube_big_edges ):
cube_big_edges[ face_edge ] = str( ( odd_even_total + 1 ) % 2 )
# now that all edge values are filled in, doublecheck that every face is odd (quadruplecheck actually  inefficiently  because I lookup by edges)
for edge in edges:
odd_even_total = 0
face = faces[ edge ]
for face_edge in face:
if face_edge in cube_big_edges:
odd_even_total = odd_even_total + int( cube_big_edges[ face_edge ] )
if odd_even_total % 2 == 0:
cube_error_file.write( 'EVEN FACE ERROR: ' )
for face_edge in face:
cube_error_file.write( face_edge + ':' + cube_big_edges[ face_edge ] + ',' )
cube_error_file.write( 'n' )
cube_error_file.write( 'CUBE1: ' + ','.join( cube1_list ) + 'n' )
cube_error_file.write( 'CUBE2: ' + ','.join( cube2_list ) + 'n' )
# output the ndimensional cube
cube_big_list = []
for edge in edges:
cube_big_list.append( edge + cube_big_edges[ edge ] )
cube_big_file.write( ','.join( cube_big_list ) + 'n' )
cube_big_file.close()
Coded in desperation to test the conjecture for 3 & 4 dimensions. Conditional programming is cool! helper.scala autogenerated the legal 4cycles.
Dependency: Download the SWIprolog package
sudo aptget prolog
This can be run in the prolog console:
swipl s square.pl
with the following commands.
findall([A,B,C,D,E,F,G,H,I,J,K,L], cube(A,B,C,D,E,F,G,H,I,J,K,L), List), length(List, Len).
findall([E1, E2, E3, E4, E5, E6, E7, E8, E9, E10, E11, E12, E13, E14, E15, E16, E17, E18, E19, E20, E21, E22, E23, E24, E25, E26, E27, E28, E29, E30, E31, E32], dim4(E1, E2, E3, E4, E5, E6, E7, E8, E9, E10, E11, E12, E13, E14, E15, E16, E17, E18, E19, E20, E21, E22, E23, E24, E25, E26, E27, E28, E29, E30, E31, E32), List), length(List, Zen).
findall([E0000e, E0001e, E000e0, E000e1, E0010e, E0011e, E001e0, E001e1, E00e00, E00e01, E00e10, E00e11, E0100e, E0101e, E010e0, E010e1, E0110e, E0111e, E011e0, E011e1, E01e00, E01e01, E01e10, E01e11, E0e000, E0e001, E0e010, E0e011, E0e100, E0e101, E0e110, E0e111, E1000e, E1001e, E100e0, E100e1, E1010e, E1011e, E101e0, E101e1, E10e00, E10e01, E10e10, E10e11, E1100e, E1101e, E110e0, E110e1, E1110e, E1111e, E111e0, E111e1, E11e00, E11e01, E11e10, E11e11, E1e000, E1e001, E1e010, E1e011, E1e100, E1e101, E1e110, E1e111, Ee0000, Ee0001, Ee0010, Ee0011, Ee0100, Ee0101, Ee0110, Ee0111, Ee1000, Ee1001, Ee1010, Ee1011, Ee1100, Ee1101, Ee1110, Ee1111], dim5(E0000e, E0001e, E000e0, E000e1, E0010e, E0011e, E001e0, E001e1, E00e00, E00e01, E00e10, E00e11, E0100e, E0101e, E010e0, E010e1, E0110e, E0111e, E011e0, E011e1, E01e00, E01e01, E01e10, E01e11, E0e000, E0e001, E0e010, E0e011, E0e100, E0e101, E0e110, E0e111, E1000e, E1001e, E100e0, E100e1, E1010e, E1011e, E101e0, E101e1, E10e00, E10e01, E10e10, E10e11, E1100e, E1101e, E110e0, E110e1, E1110e, E1111e, E111e0, E111e1, E11e00, E11e01, E11e10, E11e11, E1e000, E1e001, E1e010, E1e011, E1e100, E1e101, E1e110, E1e111, Ee0000, Ee0001, Ee0010, Ee0011, Ee0100, Ee0101, Ee0110, Ee0111, Ee1000, Ee1001, Ee1010, Ee1011, Ee1100, Ee1101, Ee1110, Ee1111), List), length(List, Zen).
%Edges can be either 0 or 1
color(0).
color(1).
even(N) :
0 is N mod 2.
odd(N) :
1 is N mod 2.
even(A, B) :
0 is A mod 2,
0 is B mod 2.
evenSum(A, B, C, D) :
Sum = A + B + C + D,
even(Sum).
oddSum(A, B, C, D) :
Sum = A + B + C + D,
odd(Sum).
%Check that the sum of the edges of a 4cycle is odd
square(E1, E2, E3, E4) :
color(E1), color(E2), color(E3), color(E4), %seperates the statements that must be satisfied
oddSum(E1, E2, E3, E4).
%coordinates on the edge of the cube is a 3 tuple, (x,y,z) <=> Exyz
%Ee00, x = e, y = 0, z=0
%where e is a place holder for the slot that is changing, the other 2 slots are fixed
%Prolog is a declaritive language, thus we will generate the legal 4 cycles in our friend, Haskell.
cube(Ee00, E0e0, E00e,
Ee01, E0e1, E01e,
Ee10, E1e0, E10e,
Ee11, E1e1, E11e) :
square(Ee00, E0e0, Ee10, E1e0),
square(E00e, E0e0, E01e, E0e1),
square(E10e, E11e, E1e0, E1e1),
square(Ee00, E00e, E10e, Ee01),
square(E01e, Ee10, E11e, Ee11),
square(Ee01, Ee11, E0e1, E1e1).
%Count the number of cubes:
%findall ([12 edges], cube(12 edges), list)
%name variables
name something that does things
%name where the elements go
%length(list name, length of list variable)
dim4(Ee110, E101e, E0e11, E00e1, E11e0, E11e1, E10e1, Ee010, E0e01, Ee100, Ee111, E00e0, E111e, E110e, E0e00, E011e, Ee101, Ee011, E001e, Ee000, E010e, E01e1, E100e, E1e00, E1e11, E000e, Ee001, E0e10, E10e0, E1e01, E1e10, E01e0) :
square(E000e, E001e, E00e0, E00e1),
square(E0e00, E010e, E0e01, E000e),
square(E01e0, E0e00, E0e10, E00e0),
square(E100e, Ee001, Ee000, E000e),
square(Ee000, E00e0, E10e0, Ee010),
square(E0e00, E1e00, Ee000, Ee100),
square(E01e0, E010e, E011e, E01e1),
square(E001e, E0e10, E0e11, E011e),
square(E0e01, E00e1, E0e11, E01e1),
square(E101e, Ee011, E001e, Ee010),
square(Ee001, Ee011, E10e1, E00e1),
square(E1e01, E0e01, Ee001, Ee101),
square(E100e, E101e, E10e1, E10e0),
square(E100e, E1e01, E1e00, E110e),
square(E1e00, E10e0, E1e10, E11e0),
square(E010e, Ee101, E110e, Ee100),
square(E01e0, Ee110, Ee100, E11e0),
square(Ee110, E0e10, E1e10, Ee010),
square(E110e, E11e1, E11e0, E111e),
square(E101e, E1e11, E1e10, E111e),
square(E1e01, E1e11, E10e1, E11e1),
square(Ee110, Ee111, E111e, E011e),
square(Ee101, Ee111, E11e1, E01e1),
square(Ee011, E1e11, Ee111, E0e11).
The rest of this code is large amounts of vertices, extending this to higher dimensions: square.pl
generate_hypercube.py:: Graph / Set Theoretic Exploration The (U,V,E) definitions of the graph by power set / Hamming distance edge enumeration is what we’ll focus on.
Here is the summary (delta operator in E is meant to be symmetric set difference):
Here was my earlier attempt at stating the method, which has writtenout examples of (Q_2) and (Q_3):
altcube.py:: String typed ndimensional cube (legal 4cycles)
If you represent the vertices as strings, for a hypercube you have:
0000 = 0 0001 = 1 0010 = 2 0011 = 3 0100 = 4 0101 = 5 0110 = 6 0111 = 7 0000 = 8 0001 = 9 0010 = 10 0011 = 11 0100 = 12 0101 = 13 0110 = 14 0111 = 15 The valid edges change 1 bit. So a face changes 2 bits.
One face would have vertices: 1001 = 9 1011 = 11 1101 = 13 1111 = 15
The edges are the ones that change 1 bit: 10011011 = 9,11 10011101 = 9,13 10111111 = 11,15 11011111 = 13,15
You can represent an edge with its odd/even by adding a bit:
110111111 = 13,15 is 1 110111110 = 13,15 is 0
So I can represent a ncube as a string of all edge values.
To create a higher dimension I would just copy the cubes each twice to get the valid cubes, adding 0 and 1 to all of their vertices respectively.
So that one edge could be:
01101011111 = 13,15 is 1 and 11101111111 = 29,31 is 1
The new edges are all 1 bit off as before.
square.pl:: Method of Computationally Generating Legal 4cycles
00 01 10 11 insert e as a placeholder, (ex:e00, 0e0, 00e) original[:pos] + ins + original[pos:] Pick all possible combinations of 4 vertices from vertices choose 4 Find legal 4 cycles: check that n slots are the same for each element of the array In the case of 3 dimensions, n=1
Example of a legal 4 cycle: (Ee00, E0e0, Ee10, E1e0)
We know it is legal because doing a columnwise AND
e00 0e0 e10 1e0 001 = 1 slot in common
Filtered Clifford Supermodules
The geometric algebra (\mathbb{G}^n) is an extension of the inner product space (\mathbb{R}^n). Each vector in (\mathbb{R}^n) is an associative algebra with a multivector in (\mathbb{G}^n), that is, it is a vector space that satisfied the following properties for all scalars (a) and (A, B, C in \mathbb{G}^n):

(A(B+C) = AB + AC), ((B + C)A = BA + CA)

((aA)B = A(aB) = a(AB))

((AB)C = A(BC))

(1A = A1 = A)

The geometric product of (\mathbb{G}^n) is linked to the algebraic structure of (\mathbb{R}^n) by (uu = u \cdot u = u ^2 \forall u \in \mathbb{R}^n)  Every orthonormal basis for (\mathbb{R}^n) determines a canonical basis for the vector space (\mathbb{G}^n).
Geometric algebra represents geometric operations on these objects with algebraic operations in (\mathbb{G}^n)
Coordinates are not used in these representations
Clifford algebras as commutative superalgebras with odd and even parts generated by the odd and even degree monomials, respectively.
If you like, you can think of the real 3D space as your vector space V with its regular inner product, and now imagine it sitting inside an algebra (Cl(V)). (I am omitting a lot of details in the interest of simplicity.) Since (V) is three dimensional, so you expect (Cl(V)) to have at least that many dimensions, but it turns out it will be (2^3) dimensional (and (2^n) dimensional for an (n) dimensional (V).)
Clearly there are many more elements in (Cl(V)) than just those in (V). The question is: do they have a useful interpretation? The answer is “yes”, because of the way things are set up. It turns out that subspaces of V can be represented as products of elements of (V) in (Cl(V)). Because multiplication is defined everywhere, you can now “multiply things in (V) with subspaces of (V)”.
How do we go between superalgebras and graphs?
We could call a ranking of a bipartite graph (A) a map (h: V(A) \rightarrow \mathbb{Z}_2) that gives (A) the additional structure of a ranked poset on (A) via (h) as the rank function.
If you have said ranked poset and rank function (h), then we can identify (A) as the Hasse diagram of the ranked poset.
Proof from Yan Zhang’s thesis
Instead of choosing 4 element sets as is outlined below in the Graph/Set theoretic approach, Yan defined an  E  element binary string (sort of like a bit mask) for each cycle with a 1 in each position where the corresponding edge belongs to that cycle (It’s easy to think about or draw in 2 / 3 / 4 dimensions). The become the rows of a matrix. You can define the coloring of edges as an  E  element 0/1 column vector and it is then possible to make the leap to say that the product of the matrix and the vectors will be a row vector of ones in the case of a successful odd dashing. Playing with that should make it clear that the rank of that matrix defines the dimensionality of the cycle space, which is equivalent to a number of similar linear algebra formulations. 
Additional Insights
Recursive Generation of Faces and Edges
Credit: Chris Walker
(v_n = 2^n) (e_n = 2e_{n1}+v_{n1}) (f_n = 2f_{n1}+e_{n1})
A Promising Method & Proven Bijection
(\mathbb{Z}^E_2 \rightarrow \mathbb{Z}^F_2/\mathbb{Z}^1_1)
In the case of n = 3, we have edges and 6 faces
(12 \rightarrow 6+1)
(2^{edges  faces})
(Rank Nullity Theorem)
The preimage of a point under a linear map is always the size of the kernel, thus there are the same number of even and odd legal colorings, and the difference between 2 odd cubes is always even.
Alex’s Thoughts
A null connection and its inverse give a higher order null connection, where null connection means none of the edges connecting subcubes are value 1:
Credit: Alex Ray
Clean enumeration such that each unique (2^n1) length binary string straightforwardly generates a unique solution.
Credit: Alex Ray
Conclusion
You know those problems which you know you shouldn’t be thinking about but keep bugging you so you occasionally work on them in desperation and then forcibly shove them to the backburner?
This is one of those problems. We’ve conquered it.