// assoc2.c  Christian van den Bosch  2002

#define E(H,I,J) ((H>>(8*I+2*J))&3)
#define S(H,G,I,J) (t[G][E(H,I,J)]<<u[G][J])
#define TRANSFORM(X,Y) \
 (S(X,Y,0,0)|S(X,Y,0,1)|S(X,Y,0,2)|S(X,Y,0,3))<<v[Y][0]|\
 (S(X,Y,1,0)|S(X,Y,1,1)|S(X,Y,1,2)|S(X,Y,1,3))<<v[Y][1]|\
 (S(X,Y,2,0)|S(X,Y,2,1)|S(X,Y,2,2)|S(X,Y,2,3))<<v[Y][2]|\
 (S(X,Y,3,0)|S(X,Y,3,1)|S(X,Y,3,2)|S(X,Y,3,3))<<v[Y][3]

#define COMMU 8
#define ANTIC 4
#define ASSOC 2
#define ANTIA 1

main()
{
  int t[24][4], u[24][4], v[24][4]; // transforms with various bitshift counts
  unsigned long q[24][16]; // binop counts [max iso class size][prop permutes]
  int h, i, j, k; // various counters
  int c[4]={0,0,0,0}, d[4]={0,0,0,0};
  int b, o, p;
  unsigned long l, m[24], n; // holders for binops

  // find the bijective (invertible) transformations...
  i=b=0;
  while( i<4 ) // fall out of loop when counter is done
  {
    for( i=0; i<4; ++i )
    {
      d[i]=-1;
    }

    o = 1;
    for( i=0; i<4; ++i ) // as we generate the inverse,
    {                     // check that each write
      if( d[c[i]] != -1 ) // is to fresh pasture
        o = 0;            // if not, we don't want it
      else
        d[c[i]] = i;      // if it is, it's okay for now.
    }
    if( o ) // we have a bijection.
    {
      for( j=0; j<4; ++j )
      v[b][j] = 4 * (u[b][j] = 2 * (t[b][j] = c[j])) ;
      ++b;
    }
    for( i=0; i<4; ++i )
    {
      ++c[i];
      if( c[i] %= 4 )
      {
        break;
      }
    }
  }

  for( i=0; i<b; ++i )
    for( j=0; j<16; ++j )
      q[i][j] = 0;

  // got transformations, let's look at binary operations
  // (binary meaning taking two parameters! oh! the humanity!)
  n=0;
  do
  {
    // make our table of operations isomorphic to n
    for( i=0; i<b; ++i)
    {
      m[i]=TRANSFORM(n,i);
      if( n>m[i] )
      {
        goto next_n;
      }
    }
    // now sort our morphed operations
    for( i = 0; i<b; ++i )
    {
      j=i;
      for( k=i+1; k<b; ++k )
        if( m[k]<m[j] )
          j=k;
      l=m[j];
      m[j]=m[i];
      m[i]=l;
    }

    k=l=-1;
    for( i=0; i<b; ++i )
    {
      if( l!=m[i] )
      {
        l=m[i];
        ++k;
      }
    }

    // now let's see what properties binary operations in this isomorphism have
    p= COMMU | ANTIC | ASSOC | ANTIA; // assume all true until proven false
    for( i=0; i<4; ++i )
      for( j=i+1; j<4; ++j )
        if( E(n,i,j) != E(n,j,i) )
          p &= ~COMMU;
        else
          p &= ~ANTIC;

    for( h=0; h<4; ++h )
      for( i=0; i<4; ++i )
        for( j=0; j<4; ++j )
          if( E(n,E(n,h,i),j) != E(n,h,E(n,i,j)) )
            p &= ~ASSOC;
          else
            p &= ~ANTIA;

    l=-1;
    if( k<8 ) // show members of the smaller classes
    {
      printf( "Class size = %d, commu = %d, antic = %d, assoc = %d, antia = %d:\n",
        k+1, !!(j&8), !!(j&4), !!(j&2), !!(j&1) );
      for( h=0; h<4; ++h )
      {
        for( i=0; i<b; ++i )
        {
          if( l!=m[i] )
          {
            l=m[i];
            for( j=0; j<4; ++j )
            {
              printf( " %d", E(l,h,j) );
            }
            printf( "  " );
          }
        }
        printf( "\n" );
      }
      printf( "\n" );
    }

    ++q[k][p];
next_n:
    ++n;
  } while( n );

  // show how many isomorphisms there are of each class
  for( i=0; i<b; ++i )
    for( j=0; j<16; ++j )
      if( q[i][j] )
        printf( " %2d[%d,%d,%d,%d]: %10d\n", i+1, !!(j&8), !!(j&4), !!(j&2), !!(j&1), q[i][j] );
}

