// An exact class key for 81-card layouts under AGL(4,3), from nauty's
// canonical labelling of the point-line incidence graph (as in orbits.c).
// The key is the canonical graph itself: the 1080 lines as triples of
// canonical point positions, sorted, so two layouts get the same key exactly
// when they're equivalent. Also prints each layout's sets and how many whole
// 27-card hyperplanes it contains.
//
//   cc -O2 -I$NAUTY -o canon4 canon4.c $NAUTY/nauty.a
//   ./canon4 < layouts.txt | sort -u -k1,1 | wc -l    # the number of classes
// Input: one layout per line, 81 '0'/'1' characters.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "nausparse.h"

#define P 81
#define NL 1080
#define NV (P + NL)
static int third[P][P], line_pts[NL][3], pt_lines[P][40];
static sparsegraph g;

static int cmp3(const void* a, const void* b) { return memcmp(a, b, 3); }

int main(void) {
  for (int a = 0; a < P; a++)
    for (int b = 0; b < P; b++) {
      int c = 0, pa = a, pb = b, w = 1;
      for (int i = 0; i < 4; i++, pa /= 3, pb /= 3, w *= 3) c += ((6 - pa % 3 - pb % 3) % 3) * w;
      third[a][b] = c;
    }
  int nl = 0, deg[P] = {0};
  for (int a = 0; a < P; a++)
    for (int b = a + 1; b < P; b++) {
      int c = third[a][b];
      if (c > b) {
        line_pts[nl][0] = a; line_pts[nl][1] = b; line_pts[nl][2] = c;
        pt_lines[a][deg[a]++] = nl; pt_lines[b][deg[b]++] = nl; pt_lines[c][deg[c]++] = nl;
        nl++;
      }
    }
  SG_INIT(g);
  SG_ALLOC(g, NV, 6 * NL, "main");
  g.nv = NV; g.nde = 6 * NL;
  size_t pos = 0;
  for (int v = 0; v < NV; v++) {
    g.v[v] = pos;
    if (v < P) { g.d[v] = 40; for (int i = 0; i < 40; i++) g.e[pos++] = P + pt_lines[v][i]; }
    else { g.d[v] = 3; for (int i = 0; i < 3; i++) g.e[pos++] = line_pts[v - P][i]; }
  }
  // the 120 hyperplanes: normal w (first nonzero digit 1), offset t
  static char hyp[120][P];
  int nh = 0;
  for (int w = 1; w < P; w++) {
    int d[4] = {w % 3, w / 3 % 3, w / 9 % 3, w / 27}, lead = 0;
    while (!d[lead]) lead++;
    if (d[lead] != 1) continue;
    for (int t = 0; t < 3; t++, nh++)
      for (int x = 0; x < P; x++)
        hyp[nh][x] = (d[0] * (x % 3) + d[1] * (x / 3 % 3) + d[2] * (x / 9 % 3) + d[3] * (x / 27)) % 3 == t;
  }
  char buf[256], in[P];
  int lab[NV], ptn[NV], orbits[NV], at[NV];
  unsigned char key[NL][3];
  DEFAULTOPTIONS_SPARSEGRAPH(opt);
  opt.getcanon = TRUE; opt.defaultptn = FALSE;
  statsblk st;
  while (fgets(buf, sizeof buf, stdin)) {
    int k = 0;
    for (int v = 0; v < P; v++) k += in[v] = buf[v] == '1';
    int i = 0;
    for (int v = 0; v < P; v++) if (in[v]) lab[i++] = v;
    for (int v = 0; v < P; v++) if (!in[v]) lab[i++] = v;
    for (int v = P; v < NV; v++) lab[i++] = v;
    for (int j = 0; j < NV; j++) ptn[j] = 1;
    if (k) ptn[k - 1] = 0;
    ptn[P - 1] = 0; ptn[NV - 1] = 0;
    SG_DECL(cg);
    sparsenauty(&g, lab, ptn, orbits, &opt, &st, &cg);
    SG_FREE(cg);
    for (int j = 0; j < NV; j++) at[lab[j]] = j;  // canonical position of each vertex
    for (int l = 0; l < NL; l++) {
      int t[3] = {at[line_pts[l][0]], at[line_pts[l][1]], at[line_pts[l][2]]};
      for (int x = 0; x < 2; x++) for (int y = 0; y < 2 - x; y++) if (t[y] > t[y + 1]) { int s = t[y]; t[y] = t[y + 1]; t[y + 1] = s; }
      key[l][0] = t[0]; key[l][1] = t[1]; key[l][2] = t[2];
    }
    qsort(key, NL, 3, cmp3);
    for (int l = 0; l < NL; l++) printf("%02x%02x%02x", key[l][0], key[l][1], key[l][2]);
    int sets = 0, full = 0;
    for (int l = 0; l < NL; l++) sets += in[line_pts[l][0]] && in[line_pts[l][1]] && in[line_pts[l][2]];
    for (int h = 0; h < 120; h++) {
      int all = 1;
      for (int x = 0; x < P && all; x++) if (hyp[h][x] && !in[x]) all = 0;
      full += all;
    }
    printf(" %d %d %d\n", k, sets, full);
  }
  return 0;
}
