
Object.extend(Array.prototype, {
  clone: function() {
    var narr = new Array(this.length);
    for(var j=0, jn=this.length; j<jn; j++) narr[j] = this[j];
    return(narr);
  }
});

Group = Class.create();
Group.prototype = {

  initialize: function(names, data, mul) {
    this.names = names;
    this.data = data;
    this.mul = mul;

    this.name = data[0];
    this.op = data[1] || '*';
    this.fontsize = data[2] || 1;
    this.n = names.length;
    this.inv = new Array(this.n);
    this.options = {};

    for(var j=0; j<this.n; j++) {
      for(var k=0; k<this.n; k++) {
        if(this.mul[j][k] == 0) {this.inv[j]=k;}
      }
    }
    this.subdiagram= null;
  /*
    this.allsubs();
    this.doinclusions();
  */
  },

  clone: function() {
    var nnames, ndata, nmul;
    nnames = this.names.clone();
    ndata = this.data.clone();
    nmul = new Array(nnames.length);
    for(var j=0; j<nnames.length; j++) {
      nmul[j] = this.mul[j].clone();
    }
    return( new Group(nnames, ndata, nmul) );
  },

  conj: function(i, j) {
    return(this.mul[this.mul[j][i]][this.inv[j]]);
  },

  comm: function(i,j) {
    return(this.mul[i][this.mul[j][this.mul[this.inv[i]][this.inv[j]]]]);
  },

  constArray: function(n, a) {
    var ar = new Array(n);
    for(var j=0; j<n; j++) ar[j] = a;
    return(ar);
  },

  array2list: function(ar) {
    var li = new Array();
    for(var j=0; j<this.n; j++)
      if(ar[j]) li.push(j);
    return(li);
  },

  setcomms: function(s1, s2) {
    var n = this.n, j, k, jn, kn;
    var inp = this.constArray(n, 0);
    var l1 = this.array2list(s1);
    var l2 = this.array2list(s2);
    for(j=0, jn=l1.length; j<jn; j++)
      for(k=0, kn=l2.length; k<kn; k++)
        inp[this.comm(l1[j], l2[k])] = 1;
    return(inp);
  },

  centralizer: function(inp) {
    var n = this.n;
    var mul = this.mul;
    var sub = this.constArray(n,1);
    var ckme = this.array2list(inp);
    var j, k;
    var len = ckme.length;
    for(k=0; k<n; k++) {
      var ok = true;
      j=0;
      while(ok && j<len) {
        if(mul[ckme[j]][k] != mul[k][ckme[j]]) {
          ok=false;
	  sub[k]=0;
        }
        j++;
      }
    }
    return(sub);
  },

  doinclusions: function() {
    if(typeof(this.sublist) == "undefined") this.allsubs();
    if(typeof(this.incs) != "undefined") return;
    var slist = this.sublist.clone();
    var incs = new Array();
    var slen = slist.length;
    slist.sort(function(a,b) { return(a.lst.length-b.lst.length) });

    var subsized = new Array();
    var size=-1, idx= -1;

    /* Partition subgroups by size */
    for(var j=0; j<slen; j++) {
      var mysz = slist[j].lst.length;
      if(mysz != size) {
        size = mysz;
	subsized.push(new Array());
	idx++;
      }
      subsized[idx].push(slist[j]);
    }

    /* Now by conj. classes */
    for(var j=0, jn=subsized.length; j<jn; j++) {
      /* partition these by conj classes */
      var cclasses= new Array(), idx=-1;
      while(subsized[j].length>0) {
        idx++;
	var thissub = subsized[j][0];
	cclasses.push(new Array());
	cclasses[idx].push(thissub);
	subsized[j].splice(0,1);
	/* Now grab conjugate subgroups */
	for(var k=0; k<this.n; k++) {
	  var ngens = thissub.gens.length;
	  var newgens = new Array(ngens);
	  /* Conjugate the generators */
	  for(var l=0; l<ngens; l++) 
	    newgens[l] = this.conj(thissub.gens[l], k);
	  /* See if these are included in an unusued subgroup */
	  for(var l=0, ln=subsized[j].length; l<ln; l++) 
	    if(subsized[j][l].containsSubset(newgens)) {
	      cclasses[idx].push(subsized[j][l]);
	      subsized[j].splice(l,1);
	      ln=-1;
	    }
	}
      }
      subsized[j] = cclasses;
    }
    /* Now subsized is an array by size, each an array by conj class */

    /* Now mirror the structure for groupmaker */
    var grps = new Array(), cnt=-1;
    for(var j=0, jn=subsized.length; j<jn; j++) {
      grps.push(new Array());
      cnt = 0;
      size = subsized[j][0][0].lst.length;
      for(var k=0, kn=subsized[j].length; k<kn; k++) {
        grps[j].push(new Array());
        for(var l=0, ln=subsized[j][k].length; l<ln; l++) {
	  cnt++;
          grps[j][k].push(size+"N"+cnt);
	  subsized[j][k][l].dianame = size+"N"+cnt;
	}
      }
    }

    for(var j=0; j<slen; j++) {
      var jcontainers = new Array(); 
      for(var k=j+1; k<slen; k++) {
        if(slist[k].contains(slist[j])) {
	  var stillok = true;
	  for(var jj=0, jjn=jcontainers.length; jj<jjn && stillok; jj++) {
	    if(slist[k].contains(jcontainers[jj])) stillok = false;
	  }
	  if(stillok) {
	    incs.push([slist[j].dianame, slist[k].dianame]);
	    jcontainers.push(slist[k]);
	  }
	}
      }
    }

    this.incs = incs;
    this.grps = grps;

    if(1<0 && typeof(CanvasRenderingContext2D) != 'undefined')  {
      this.subdiagram = makegroup('groupdia', grps, incs);
    }
  },

  drawsubs: function() {
    if(typeof(this.incs) == "undefined") this.doinclusions();
    if(! this.subdiagram) {
      this.subdiagram = makegroup('groupdia', this.grps, this.incs, this.sublist);
    }
    this.subdiagram.draw();
  },

  destroy: function() {
    if(this.subdiagram) this.subdiagram.clear();
  },

  allsubs: function() {
    if(typeof(this.gens) == "undefined") this.cyclicsubs();
    if(this.allsubsDone) return;
    var which = 1; // skip trivial group
    while(which < this.sublist.length) {
      for(var k= this.sublist[which].geninds.last()+1; k< this.gens.length; k++) {
        var tmpsub = this.sublist[which].addgen(this.gens[k]);
	var isold = false;
	for(var jj=0, jjn = this.sublist.length; jj<jjn && ! isold; jj++) {
	  if(tmpsub.isSame(this.sublist[jj])) isold = true;
	}
	if(! isold) {
	  tmpsub.geninds = this.sublist[which].geninds.clone();
	  tmpsub.geninds.push(k);
	  this.sublist.push(tmpsub);
        }
      }
      which++;
    }
    this.allsubsDone=true;
  },

  cyclicsubs: function() {
    var sublist = new Array();
    var used = this.constArray(this.n, 0);
    var tmpsub = new Subgroup(this, new Array(), 'gens');
    tmpsub.gensub();
    sublist.push(tmpsub);
    var gg = 1;
    while(gg<this.n) {
      var nextsub = new Subgroup(this, [gg], 'gens');
      nextsub.gensub();
      sublist.push(nextsub);
      var sublen = nextsub.lst.length;
      used[gg]=1;
      for(var k=2; k<sublen; k++) {
        if(used[nextsub.lst[k]]==0) {
	  if((sublen % k) == 0) {
	    tmpsub = new Subgroup(this, [nextsub.lst[k]], 'gens');
	    tmpsub.gensub();
            sublist.push(tmpsub);
          }
	  used[nextsub.lst[k]]=1;
	}
      }
      do gg++; while(used[gg]==1);
    }
    this.sublist = sublist;
    this.gens = new Array();
    for(var j=1, jn=sublist.length; j<jn; j++) {
      this.gens.push(sublist[j].gens[0]);
      sublist[j].geninds = new Array();
      sublist[j].geninds.push(j-1);
    }
  },

  /* Gens to perms */
  gens2perms: function() {
    this.allsubs();
    var perms = new Array();
    var mygens;
    for(var j = 0; j<this.sublist.length; j++) {
      if(this.sublist[j].lst.length == this.n) mygens =this.sublist[j];
    }
    mygens = mygens.gens;
    
    for(var j=0, jn= mygens.length; j<jn ; j++) {
      var prm = new Array(this.n);
      for(var k=0; k<this.n; k++) {
        prm[k] = this.mul[mygens[j]][k]+1;
      }
      perms.push(prm);
    }
    return('Found '+ perms.inspect());
  },

  /* Generate a homomorphism to another group based on a list of gens */
  genhom: function(range, pairs) {
    var func = this.constArray(this.n, 0);
    var mygens = new Array();
    var ok=1;
    for(var j=0; j<pairs.length; j++) {
      mygens.push(pairs[j][0]);
      func[pairs[j][0]] = pairs[j][1]; 
    }
    var mysub = new Subgroup(this, mygens, 'valueList');
    mysub.gensub({quickabort: false, 
      gensubnew: function(a,b,c) {
        func[c] = range.mul[func[a]][func[b]];
      },
      gensubold: function(a,b,c) {
        if(func[c] != range.mul[func[a]][func[b]]) {
          ok=0;
// alert('Problem looking at '+[a,b,c].inspect());
        }
      }
    }); 
    if(ok) { return(func); }
    alert('Bad hom '+func.inspect());
    return ;
  },

  /* Inputs are the group we map to, and then a list of homomorphism
     data, each of which is an element followed by where it maps to.
     Its target, in turn, is a list of pairs in the target group.
     If we want the map from Z_4 to Aut(Z_5), genlist is [[1,[[1,2]]]] */
  genconnecthom: function(target, genlist) {
    var fullmap = new Array(this.n);
    fullmap[0] = new Array(target.n);
    for(var j=0; j<target.n; j++) fullmap[0][j] = j;
    var mygens = new Array();
    for(var j=0; j<genlist.length; j++) {
      mygens.push(genlist[j][0]);
      fullmap[genlist[j][0]] = target.genhom(target, genlist[j][1]);
      if(typeof(fullmap[genlist[j][0]]) =='undefined') 
        alert('Bad hom on '+ genlist[j][0]);
    }
    var mysub = new Subgroup(this, mygens, 'valueList');
    mysub.gensub({quickabort: false,
      gensubnew: function(a,b,c) {
      // alert('Defining hom from '+[a,b,c].inspect());
        fullmap[c] = new Array(target.n);
	for(var k=0; k<target.n; k++) {
	  fullmap[c][k] = fullmap[a][fullmap[b][k]];
	}
      }
    });
/*
for(var j=0; j<this.n; j++) {
alert('Hom '+j+' is '+fullmap[j].inspect());
for(var k=0; k<target.n; k++) {
for(var l=0; l<target.n; l++) {
var fm = fullmap[j];
 // if(target.mul[fm[k]][fm[l]] != fm[target.mul[k][l]]) alert('hom 1 error '+j+': '+fm.inspect());
}}}
*/
    return(fullmap);
  }

}

/* Can create a subgroup with toggles, generators, array of values */
Subgroup = Class.create();
Subgroup.prototype = {

/* g: ambient group
   changed: if we do something, says if the set changed
   gens: a generating set, as a list
   lst: the elements as a list
  curg.drawsubs();
   toggles: the elements in a list of n on/off toggles
  
   geninds: if we have a list of generators of cyclic subgroups,
     this gives the list of indicies of the generators of this subgroup
     If there are 3 cyclic subs (including trivial), this will be 1 or 2.
     This list should be increasing.
*/

  inspect: function() {
    return('('+this.lst.length+': '+this.gens.join(', ')+')');
  },

  initialize: function(group, data, type) {
    this.g = group;
    this.changed = 0;
    this.gens = undefined;
    switch(type) {
    case 'toggles':
      this.toggles = data.clone();
      this.lst = group.array2list(data);
      this.gens = this.lst.clone();
      break;
    case 'valueList':
      this.lst = data.clone();
      this.gens = this.lst.clone();
      this.toggles = group.constArray(group.n, 0);
      for(var j=0, jlen=this.lst.length; j<jlen ; j++) this.toggles[this.lst[j]]=1;
      break;
    case 'gens':
      this.gens = data.clone();
      this.lst = data.clone();
      this.toggles = group.constArray(group.n, 0);
      for(var j=0, jlen=this.gens.length; j<jlen ; j++) this.toggles[this.gens[j]]=1;
      if(this.toggles[0]==0) {
        this.toggles[0]=1;
	this.lst.unshift(0);
      }
      this.gensub();
      break;
    }
  },

  addgen: function(newgen) {
    var genclone = this.gens.clone();
    genclone.push(newgen);
    var mysub = new Subgroup(this.g, genclone, 'gens');
    return(mysub);
  },

  contains: function(other) {
    for(var j=0, jn = other.gens.length; j<jn; j++)
      if(this.toggles[other.gens[j]]==0) return(false);
    return(true);
  },

  containsSubset: function(subset) {
    for(var j=0, jn = subset.length; j<jn; j++)
      if(this.toggles[subset[j]]==0) return(false);
    return(true);
  },

  isSame: function(other) {
    if(this.lst.length != other.lst.length) return(false);
    return(this.contains(other));
  },

  size: function() {
    return(this.lst.length);
  },

  /* generate the whole group */
  wholegroup: function() {
    var n = this.g.n;     
    if(this.lst.length<n) this.changed=1;
    for(var j=0; j<n; j++) {
      if(this.toggles[j]==0) {
        this.toggles[j]=1;
        this.lst.push(j);
      }
    }
  },

  gensub: function(opts) {
    var n = this.g.n;     
    var mul = this.g.mul;
    var selected = this.toggles;
    var sub = this.lst;
    if(typeof(opts) == 'undefined') opts={};
    if(typeof(opts.quickabort) == 'undefined') opts.quickabort=true;
    if(typeof(opts.gensubnew) == 'undefined') opts.gensubnew=Prototype.emptyFunction;
    if(typeof(opts.gensubold) == 'undefined') opts.gensubold=Prototype.emptyFunction;
    this.changed=0;
    var sofar = 0;
    if(selected[0]==0) {
      sub.push(0);
      selected[0]=1;
      this.changed=1;
    }
    var nw;
    sofar = sub.length;
    if(sofar*2>n && opts.quickabort) return(this.wholegroup());
    var j=0;
    while(j<sofar) {
      var k=j;
      while(k<sofar) {
        nw=mul[sub[j]][sub[k]];
        if(selected[nw]==0) {
          selected[nw]=1;
          this.changed=1;
          sub.push(nw);
          sofar++;
          if(sofar*2>n && opts.quickabort) return(this.wholegroup());
          opts.gensubnew(sub[j], sub[k], nw);
        } else { opts.gensubold(sub[j], sub[k], nw); }
        nw=mul[sub[k]][sub[j]];
        if(selected[nw]==0) {
          selected[nw]=1;
          this.changed=1;
          sub.push(nw);
          sofar++;
          if(sofar*2>n && opts.quickabort) return(this.wholegroup());
          opts.gensubnew(sub[k], sub[j], nw);
        } else { opts.gensubold(sub[k], sub[j], nw); }
        k++;
      }
      j++;
    }
  }

}

/*  Data for actual groups */

gorder = new Array([['Trivial', 'Trivial', 'Trivial']],
                   [['zn', 2, 'Z_2']
		   ],
                   [['zn', 3, 'Z_3']],
                   [['zn', 4, 'Z_4'], ['V_4', 'V_4','V_4']],
                   [['zn', 5, 'Z_5']],
                   [['zn', 6, 'Z_6'], ['S_3', 'S_3', 'S_3']],
                   [['zn', 7, 'Z_7']],
                   [['zn', 8, 'Z_8'], ['znprod', '4,2','Z_4 x Z_2'],
                    ['znprod', '2,2,2','Z_2 x Z_2 x Z_2'],
                    ['dn', 4,'D_4'], ['Q_8', 'Q_8', 'Q_8']],
                   [['zn', 9, 'Z_9'],['znprod', '3,3', 'Z_3 x Z_3']],
                   [['zn', 10, 'Z_10'], ['dn', 5,'D_5']],
                   [['zn', 11, 'Z_11']],
                   [['zn', 12, 'Z_12'], ['znprod', '6,2', 'Z_6 x Z_2'],
                    ['misc','A_4','A_4'], ['dn', 6, 'D_6'],
                    ['semidircyclic', [3, 4, 2], 'Z_3 : Z_4']],
                   [['zn', 13, 'Z_13']],
                   [['zn', 14, 'Z_14'],['dn',7,'D_7']],
                   [['zn', 15, 'Z_15']],
                   [['zn', 16, 'Z_16'],['znprod', '4,4', 'Z_4 x Z_4'],
['semidir', [['znprod', '4,2', 'Z_4 x Z_2'],['zn',2,'Z_2'],[[1,[[1,7],[4,4]]]]], 'Z_4 x Z_2 : Z_2'],
                    ['semidircyclic', [4,4,3], 'Z_4 : Z_4'],
                    ['znprod', '8,2', 'Z_8 x Z_2'],
                    ['semidircyclic', [8,2,5,5], 'Z_8 :_5 Z_2'],
                    ['dn', 8,'D_8'],
                    ['semidircyclic', [8,2,3,3], 'Z_8 :_3 Z_2'],
                    ['lookup16', 9, 'Z_2 . D_4'],
                    ['znprod', '4,2,2', 'Z_4 x Z_2 x Z_2'],
	    ['prod', [['dn', 4, 'D_4'],['zn',2,'Z_2']], 'D_4 x Z_2'],
	    ['prod', [['Q_8', 'Q_8', 'Q_8'],['zn',2,'Z_2']], 'Q_8 x Z_2'],
['semidir', [['Q_8', 'Q_8', 'Q_8'],['zn',2,'Z_2'],[[1,[[2,3],[4,5]]]]], 'Q_8 : Z_2'],
                    ['znprod', '2,2,2,2', 'Z_2 x Z_2 x Z_2 x Z_2']
                    ],
                   [['zn', 17, 'Z_17']],
                   [['zn', 18, 'Z_18'],['znprod','6,3','Z_6 x Z_3'],
                    ['dn', 9, 'D_9'],
['prod', [['S_3', 'S_3', 'S_3'],['zn',3,'Z_3']], 'S_3 x Z_3'],
                    ['misc', 'Z_3^2:Z_2', 'Z_3 x Z_3 : Z_2']],
                   [['zn', 19, 'Z_19']],
                   [['zn', 20, 'Z_20'],
                    ['znprod', '10,2', 'Z_10 x Z_2'],
                    ['dn', 10, 'D_10'],
                    ['semidircyclic', [5, 4, 2], 'Z_5 : Z_4'],
                    ['semidircyclic', [5, 4, 4, 2], 'Z_5 :_2 Z_4']],
                   [['zn', 21, 'Z_21'],
                    ['semidircyclic', [7, 3, 2], 'Z_7 : Z_3']],
                   [['zn', 22, 'Z_22'],['dn',11,'D_11']],
                   [['zn', 23, 'Z_23']],
                   [['zn', 24, 'Z_24'],['dn',12,'D_12'],
              ['znprod', '12,2', 'Z_12 x Z_2'],
              ['znprod', '6,2,2', 'Z_6 x Z_2 x Z_2'], ['S_4', 'S_4', 'S_4'],
['prod', [['S_3', 'S_3', 'S_3'],['zn',4,'Z_4']], 'S_3 x Z_4'],
['prod', [['S_3', 'S_3', 'S_3'],['V_4','V_4','V_4']], 'S_3 x V_4'],
['prod', [['Q_8', 'Q_8', 'Q_8'],['zn',3,'Z_3']], 'Q_8 x Z_3'],
['prod', [['dn', 4, 'D_4'],['zn',3,'Z_3']], 'D_4 x Z_3'],
['prod', [['misc', 'A_4', 'A_4'],['zn',2,'Z_2']], 'A_4 x Z_2'],
['misc', 'SL_2(Z_3)', 'SL_2(Z_3)'],
['semidircyclic', [3,8,2], 'Z_3 : Z_8'],
['semidircyclic', [6,4,5], 'Z_6 : Z_4'],
['semidir', [['zn',3,'Z_3'],['Q_8', 'Q_8', 'Q_8'],[[2,[[1,2]]],[4,[[1,1]]]]], 'Z_3 : Q_8'],
['semidir', [['znprod','6,2','Z_6 x Z_2'],['zn', 2, 'Z_2'],[[1,[[1,5],[6,9]]]]], 'Z_6 x Z_2 : Z_2']],
                   [['zn', 25, 'Z_25'],['znprod', '5,5', 'Z_5 x Z_5']],
                   [['zn', 26, 'Z_26'],['dn',13,'D_13']],
                   [['zn', 27, 'Z_27'],['znprod', '9,3', 'Z_9 x Z_3'],
['znprod', '3,3,3', 'Z_3 x Z_3 x Z_3'],['semidircyclic', [9,3,4],['Z_9 : Z_3']],
['misc', 'heis3', 'Z_3 x Z_3 : Z_3']],
	   [['zn', 28, 'Z_28'],['znprod', '14,2', 'Z_14 x Z_2'],
['dn',14,'D_14'],['semidircyclic', [7,4,6], 'Z_7 : Z_4']],
                   [['zn', 29, 'Z_29']],
	   [['zn', 30, 'Z_30'], ['dn',15,'D_15'],
['prod', [['S_3', 'S_3', 'S_3'], ['zn', 5, 'Z_5']], 'S_3 x Z_5'],
['prod', [['dn', 5, 'D_5'], ['zn',3,'Z_3']], 'D_5 x Z_3' ]]
                   );


triv = new Array(['<i>e</i>'], ['Trivial Group','*'], [[0]]);

v4 = new Array(['<i>e</i>','<i>a</i>','<i>b</i>','<i>c</i>'], 
/* v4 = new Array(['<i>e</i>','<span class="math">\\alpha</span>','<span class="math">\\beta</span">','<span class="math">\\gamma</span>'],  */
 ['<i>V</i><sub>4</sub>','*'],
 [[0,1,2,3],[1,0,3,2],[2,3,0,1],[3,2,1,0]]);


s3 = new Group(['<i>e</i>','(12)','(13)','(23)','(123)','(132)'],
               ['<i>S</i><sub>3</sub>'],
 [[0,1,2,3,4,5],
  [1,0,5,4,3,2],
  [2,4,0,5,1,3],
  [3,5,4,0,2,1],
  [4,2,3,1,5,0],
  [5,3,1,2,0,4]]);

a4 = new Array(['<i>i</i>', '(234)', '(243)', '(12)(34)',
                '(123)', '(124)', '(132)', '(134)', 
                '(13)(24)', '(142)', '(143)', '(14)(23)'],
               ['<i>A</i><sub>4</sub>','*'],
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
[1, 2, 0, 5, 3, 4, 7, 8, 6, 11, 9, 10],
[2, 0, 1, 4, 5, 3, 8, 6, 7, 10, 11, 9],
[3, 6, 9, 0, 7, 10, 1, 4, 11, 2, 5, 8],
[4, 8, 10, 2, 6, 11, 0, 5, 9, 1, 3, 7],
[5, 7, 11, 1, 8, 9, 2, 3, 10, 0, 4, 6],
[6, 9, 3, 10, 0, 7, 4, 11, 1, 8, 2, 5],
[7, 11, 5, 9, 1, 8, 3, 10, 2, 6, 0, 4],
[8, 10, 4, 11, 2, 6, 5, 9, 0, 7, 1, 3],
[9, 3, 6, 7, 10, 0, 11, 1, 4, 5, 8, 2],
[10, 4, 8, 6, 11, 2, 9, 0, 5, 3, 7, 1],
 [11, 5, 7, 8, 9, 1, 10, 2, 3, 4, 6, 0]]);

s4 = new Array(['<i>i</i>', '(34)', '(23)', '(234)', '(243)', '(24)',
                '(12)', '(12)(34)', '(123)', '(1234)', '(1243)', '(124)', 
                '(132)', '(1342)', '(13)', '(134)', '(13)(24)', '(1324)',
                '(1432)', '(142)', '(143)', '(14)', '(1423)', '(14)(23)' ],
               ['<i>S</i><sub>4</sub>','*',0.6],
[[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23],
[1,0,3,2,5,4,7,6,9,8,11,10,13,12,15,14,17,16,19,18,21,20,23,22],
[2,4,0,5,1,3,8,10,6,11,7,9,14,16,12,17,13,15,20,22,18,23,19,21],
[3,5,1,4,0,2,9,11,7,10,6,8,15,17,13,16,12,14,21,23,19,22,18,20],
[4,2,5,0,3,1,10,8,11,6,9,7,16,14,17,12,15,13,22,20,23,18,21,19],
[5,3,4,1,2,0,11,9,10,7,8,6,17,15,16,13,14,12,23,21,22,19,20,18],
[6,7,12,13,18,19,0,1,14,15,20,21,2,3,8,9,22,23,4,5,10,11,16,17],
[7,6,13,12,19,18,1,0,15,14,21,20,3,2,9,8,23,22,5,4,11,10,17,16],
[8,10,14,16,20,22,2,4,12,17,18,23,0,5,6,11,19,21,1,3,7,9,13,15],
[9,11,15,17,21,23,3,5,13,16,19,22,1,4,7,10,18,20,0,2,6,8,12,14],
[10,8,16,14,22,20,4,2,17,12,23,18,5,0,11,6,21,19,3,1,9,7,15,13],
[11,9,17,15,23,21,5,3,16,13,22,19,4,1,10,7,20,18,2,0,8,6,14,12],
[12,18,6,19,7,13,14,20,0,21,1,15,8,22,2,23,3,9,10,16,4,17,5,11],
[13,19,7,18,6,12,15,21,1,20,0,14,9,23,3,22,2,8,11,17,5,16,4,10],
[14,20,8,22,10,16,12,18,2,23,4,17,6,19,0,21,5,11,7,13,1,15,3,9],
[15,21,9,23,11,17,13,19,3,22,5,16,7,18,1,20,4,10,6,12,0,14,2,8],
[16,22,10,20,8,14,17,23,4,18,2,12,11,21,5,19,0,6,9,15,3,13,1,7],
[17,23,11,21,9,15,16,22,5,19,3,13,10,20,4,18,1,7,8,14,2,12,0,6],
[18,12,19,6,13,7,20,14,21,0,15,1,22,8,23,2,9,3,16,10,17,4,11,5],
[19,13,18,7,12,6,21,15,20,1,14,0,23,9,22,3,8,2,17,11,16,5,10,4],
[20,14,22,8,16,10,18,12,23,2,17,4,19,6,21,0,11,5,13,7,15,1,9,3],
[21,15,23,9,17,11,19,13,22,3,16,5,18,7,20,1,10,4,12,6,14,0,8,2],
[22,16,20,10,14,8,23,17,18,4,12,2,21,11,19,5,6,0,15,9,13,3,7,1],
 [23,17,21,11,15,9,22,16,19,5,13,3,20,10,18,4,7,1,14,8,12,2,6,0]]);


t12 = new Array(['<i>e</i>','<i>b</i>','<i>b</i><sup>2</sup>',
                 '<i>b</i><sup>3</sup>','<i>a</i>','<i>a</i><i>b</i>',
                 '<i>a</i><i>b</i><sup>2</sup>','<i>a</i><i>b</i><sup>3</sup>',
                 '<i>a</i><sup>2</sup>','<i>a</i><sup>2</sup><i>b</i>',
                 '<i>a</i><sup>2</sup><i>b</i><sup>2</sup>',
                 '<i>a</i><sup>2</sup><i>b</i><sup>3</sup>'],
                ['<b>Z</b><sub>3</sub>:<b>Z</b><sub>4</sub>', '*'], [
[0,1,2,3,4,5,6,7,8,9,10,11],
[1,2,3,0,9,10,11,8,5,6,7,4],
[2,3,0,1,6,7,4,5,10,11,8,9],
[3,0,1,2,11,8,9,10,7,4,5,6],
[4,5,6,7,8,9,10,11,0,1,2,3],
[5,6,7,4,1,2,3,0,9,10,11,8],
[6,7,4,5,10,11,8,9,2,3,0,1],
[7,4,5,6,3,0,1,2,11,8,9,10],
[8,9,10,11,0,1,2,3,4,5,6,7],
[9,10,11,8,5,6,7,4,1,2,3,0],
[10,11,8,9,2,3,0,1,6,7,4,5],
[11,8,9,10,7,4,5,6,3,0,1,2]]);

q8 = new Group(['1','-1','<i>i</i>','-<i>i</i>','<i>j</i>','-<i>j</i>','<i>k</i>','-<i>k</i>'],
 ['<i>Q</i><sub>8</sub>'],
[[0,1,2,3,4,5,6,7],
 [1,0,3,2,5,4,7,6],
 [2,3,1,0,6,7,5,4],
 [3,2,0,1,7,6,4,5],
 [4,5,7,6,1,0,2,3],
 [5,4,6,7,0,1,3,2],
 [6,7,4,5,3,2,1,0],
 [7,6,5,4,2,3,0,1]]);

function cyclic(n) {
  var names = new Array(n);
  var data = new Array('<b>Z</b><sub>'+n+'</sub>', '+');
  var table = new Array(n);
  var j,k;
  for(j=0; j<n; j++) {
    names[j] = j;
    table[j] = new Array(n);
  }
  for(j=0; j<n; j++) {
    for(k=0; k<n; k++) {
       table[j][k] = (j+k) % n;
    }
  }
  return(new Group(names, data, table));
}

function dn(n) {
  var data = new Array('<i>D</i><sub>'+n+'</sub>', '*');
  var names = new Array(2*n);
  var table = new Array(2*n);
  var j,k;
  for(j=0; j<n; j++) {
    names[j] = '<i>R</i><sup>'+j+'</sup>';
    table[j] = new Array(2*n);
  }
  names[0] = '<i>e</i>';
  names[1] = '<i>R</i>';
  for(j=0; j<n; j++) {
    names[j+n] = '<i>v</i><sub>'+(j+1)+'</sub>';
    table[j+n] = new Array(2*n);
  }
  if(n==3) {
    names[3] = '|';names[4] = '/';names[5] = '\\';
  }
  if(n==4) {
    names[4] = '\\'; names[5] = '|';
    names[6] = '/'; names[7] = '&minus;';
  }
  
  for(j=0; j<2*n; j++) {
    for(k=0; k<2*n; k++) {
      var a1 = j % n;
      var b1 = k % n;
      var a2 = (j-a1)/n;
      var b2 = (k-b1)/n;
      table[j][k] = ((a1+(n+Math.pow((-1),a2))*b1) %n) + n*((a2+b2) % 2);
    }
  }
  
  return(new Group(names, data, table));
}

/* Semi-direct product Z_n : Z_m where 1 maps to the automorphism
   1->g */

function semiDirCyclic(n,m,g,sb) {
  var subsc = '';
  if(typeof(sb) != 'undefined') subsc = '_'+sb;
  var data = new Array('<b>Z</b><sub>'+n+'</sub> :'
                       +subsc+' <b>Z</b><sub>'+m+'</sub>', '(+;+)');
  var gn = n*m;
  var names = new Array(gn);
  var table = new Array(gn);
  var j,k;
  for(j=0; j<gn; j++) {
    var a1 = j % n;
    var a2 = (j-a1)/n;
    table[j] = new Array(gn);
    names[j] = '('+a1+';'+a2+')';
    for(k=0; k<gn; k++) {
      var b1 = k % n;
      var b2 = (k-b1)/n;
      var c1 = b1;
      for(var l=0; l<a2; l++) { c1 = (c1*g) % n }
      c1 = (c1+a1) % n;
      var c2 = (a2+b2) % m;
      table[j][k] = n*c2+c1;
    }
  }

  return(new Group(names, data, table));
}

/* Semi-direct product of g1 : g2 */
function semiDirProduct(g1,g2,connectData) {
  var data = [g1.name+" : "+g2.name, '('+g1.op+":"+g2.op+')'];
  var gn = g1.n*g2.n;
  var names = new Array(gn);
  var table = new Array(gn);
  var fullmap = g2.genconnecthom(g1, connectData);
  var j,k;
  for(j=0; j<gn; j++) {
    var a1 = j % g1.n;
    var a2 = (j-a1)/g1.n;
    table[j] = new Array(gn);
    names[j] = '('+g1.names[a1]+';'+g2.names[a2]+')';
    for(k=0; k<gn; k++) {
      var b1 = k % g1.n;
      var b2 = (k-b1)/g1.n;
      var c1 = g1.mul[a1][fullmap[a2][b1]];
      var c2 = g2.mul[a2][b2];
      table[j][k] = g1.n*c2+c1;
    }
  }
  return(new Group(names, data, table));
}

function hol(n) {
  var data = new Array('Aff(<b>Z</b><sub>'+n+'</sub>)', 'o');
  var znst = new Array(1);
  znst[0] = 1;
  var j,k;
  for(j=2; j<n; j++) {
    if(gcd(j,n)==1) {
      znst.push(j);
    }
  }
  var phin = znst.length;
  var table = new Array(n*phin);
  var names = new Array(n*phin);
  for(j=0; j<phin; j++) {
    for(k=0; k<n; k++) {
      names[j*n+k] = znst[j]*n +k;
      table[j*n+k] = new Array(n*phin);
    }
  }
  var cnt=0;
  for(j=0; j<n*phin; j++) {
    for(k=0; k<n*phin; k++) {
      var a2 = names[j] % n;
      var b2 = names[k] % n;
      var a1 = (names[j]-a2)/n;
      var b1 = (names[k]-b2)/n;
      var newa = (a1*b1) % n;
      var newb = (b2*a1+a2) % n;
      
      var newi = assoc(newa*n+newb, names);
      table[j][k] = newi;
    }
  }
  for(j=0; j<n*phin; j++) {
    var b2 = names[j] % n;
    var b1 = (names[j]-b2)/n;
    names[j] = (b1 == 1) ? '<i>x</i>' : b1+'<i>x</i>';
    if(b2 >0) {
      names[j] += '+'+b2;
    }
  }
  
  return(new Group(names, data, table));
}

function znstar(n) {
  var names = new Array(1), j,k;
  names[0] = 1;
  for(j=2; j<n; j++) {
    if(gcd(j,n)==1) {
      names.push(j);
    }
  }
  var phin = names.length;
  var data = new Array('<b>Z</b><sup>*</sup><sub><i>'+n+'</i></sub>', '*');
  var table = new Array(phin);
  var toindex = new Array(n);
  for(j=0; j<phin; j++) {
    table[j] = new Array(phin);
    toindex[names[j]] = j;
  }
  for(j=0; j<phin; j++) {
    for(k=0; k<phin; k++) {
       var newval = (names[j]*names[k]) % n;
       table[j][k] = toindex[newval];
    }
  }
  return(new Group(names, data, table));
}

function cycprod(ni) {
  var myg = '';
  if(ni.length==0) {
    return(triv);
  }
  myg = cyclic(ni[0]);
  var goper='(+';

  var nfacts = ni.length;
  for(var j=1; j<nfacts; j++) {
    myg = prodgroup(myg, cyclic(ni[j]));
    goper += ',+';
  }
  var names = myg.names;
  for(var j=0; j<myg.n; j++) {
    var jt = j;
    var nna='('+ (jt % ni[0]);
    for(var k=1; k<nfacts; k++) {
      jt=(jt - (jt % ni[k-1]))/ni[k-1];
      nna += ','+(jt % ni[k]);
    }
    names[j] = nna+')';
  }
  goper = goper+')';
  
  return(new Group(names, [myg.name, goper], myg.mul));
}
  
function prodgroup(g1, g2) {
  var l1 = g1.n;
  var l2 = g2.n;
  var sz = l1*l2;
  var names = new Array(sz);
  var data = new Array(2);
  data[0] = g1.name +' x '+ g2.name;
  data[1] = '('+g1.op +','+g2.op+')';
  var table = new Array(sz), a1,a2,a3,b1,b2,b3,j,k;
  for(j=0; j<sz; j++) {
    names[j] = '('+g1.names[j % l1 ]+','+g2.names[(j-(j % l1))/l1]+')';
    table[j] = new Array(sz);
  }
  var m1=g1.mul;
  var m2=g2.mul;
  for(j=0; j<sz; j++) {
    for(k=0; k<sz; k++) {
       a1 = j % l1;
       b1 = (j-a1)/l1;
       a2 = k % l1;
       b2 = (k-a2)/l1;
       a3 = m1[a1][a2];
       b3 = m2[b1][b2];
       table[j][k] = b3*l1+a3;
    }
  }
  return(new Group(names, data, table));
}

function mysterygroup(bnd, num) {
  var cnt=0, j=0, gol=gorder.length;
  while(j<gol && j<bnd) {
    cnt+= gorder[j].length;
    j++;
  }
  setseed(num);
  for(j=0; j<10; j++) rnd();
  var pickme = rndn(cnt);
  j= -1; cnt=0;
  while(cnt<=pickme) {
    j++;
    cnt+= gorder[j].length;
  }
  cnt -= gorder[j].length;
  var gent = gorder[j][pickme-cnt];
  GType = gent[0];
  var thisg = getagroup(gent[1]).clone();
 /* alert('Group is '+thisg.name);  */
  thisg.name = '??';
  thisg.op = '*';
  for(j=0; j<thisg.n; j++) thisg.names[j] = numname(j);
  var permu = permutation(thisg.n);
/* alert('Permutation: '+permu.inspect()); */
  var k, tmp;
  var newmul = new Array(thisg.n);
  var newinv = new Array(thisg.n);
  for(j=0; j<thisg.n; j++) { newmul[j] = new Array(thisg.n);}
  for(j=0; j<thisg.n; j++) {
    newinv[permu[j]] = permu[thisg.inv[j]];
    for(k=0; k<thisg.n; k++) {
      newmul[permu[j]][permu[k]] = permu[thisg.mul[j][k]];
    }
  }
  thisg.mul = newmul;
  thisg.inv = newinv;
  return(thisg);
}

function numname(a) {
  var alpha = 'eabcdfghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
  return alpha.charAt(a);
}

rnd.seed = 1234;
function setseed(sed) {
  rnd.seed = sed;
}

// Modification of:
// The Central Randomizer 1.3 (C) 1997 by Paul Houle (paul@honeylocust.com)
// See:  http://www.honeylocust.com/javascript/randomizer.html
function rnd() {
        rnd.seed = (rnd.seed*9301+49297) % 233280;
        return rnd.seed/(233280.0);
}

// random number from 0 to n-1
function rndn(n) {
  return( Math.floor(n*rnd()));
}

// random list from 0 to n-1 fixing 0
function permutation(n) {
  var permu = new Array(n);
  var j,k,tmp;
  for(j=0; j<n; j++) permu[j] = j;
  for(j=1; j<n; j++) {
    k = rndn(n-j)+1;
    tmp = permu[j]; permu[j] = permu[k]; permu[k] = tmp;
  }
  return(permu);
}


function gcd(a,b) {
  var temp;
  if(a==0 && b==0) return(0);
  if(a < 0) {a = -a;};
  if(b < 0) {b = -b;};
  if(b > a) {temp = a; a = b; b = temp;};
  while (true) {
    if(b == 0) {return a;};
    a %= b;
    if(a == 0) {return b;};
    b %= a;
  };
  return b;
}

function getagroup(myinp) {
  var thisg;
  switch (GType) {
  case 'zn':
    var n = getn(myinp);
    if(n==0) {
      alert('You need to enter a positive integer for n');
    } else {
      thisg = cyclic(n);
    }
    break;
  case 'znstar':
    var n = getn(myinp);
    if(n==0) {
      alert('You need to enter a positive integer for n');
    } else {
      thisg = znstar(n);
    }
    break;
  case 'dn':
    var n = getn(myinp);
    if(n<3) {
      alert('You need to enter an integer for n > 2');
    } else {
      thisg = dn(n);
    }
    break;
  case 'holn':
    var n = getn(myinp);
    if(n==0) {
      alert('You need to enter a positive integer for n');
    } else {
      thisg = hol(n);
    }
    break;
  case 'prod':
    var g1 = myinp[0];
    var g2 = myinp[1];
    GType = g1[0]; g1 = getagroup(g1[1]);
    GType = g2[0]; g2 = getagroup(g2[1]);
    thisg = prodgroup(g1, g2);
    break;
  case 'semidir':
    var g1 = myinp[0];
    var g2 = myinp[1];
    GType = g1[0]; g1 = getagroup(g1[1]);
    GType = g2[0]; g2 = getagroup(g2[1]);
    thisg = semiDirProduct(g1, g2, myinp[2]);
    break;
  case 'mystery':
    var bnd = getn(myinp);
    if(typeof(bnd) == 'undefined' || bnd<=0) bnd=100;
    var gnum = getformvalue('num');
    gnum = parseInt(gnum);
    if(isNaN(gnum) || gnum < 0) { gnum=100;}
    thisg = mysterygroup(bnd, gnum);
    break;
  case 'znprod':
    var ni = getnlist(myinp);
    thisg = cycprod(ni);
    break;
  case 'semidircyclic':
    thisg = semiDirCyclic(myinp[0], myinp[1],myinp[2], myinp[3]);
    break;
  case 'lookup16':
    thisg = d16[myinp-1];
    GType = 'order';
    break;
  case 'order':
    var gord = getformvalue('groupord');
    var gindnum = getformvalue('gentry');
    var gent = gorder[gord-1][gindnum];
    GType = gent[0];
    thisg = getagroup(gent[1]);
    break;
  case "Trivial": thisg=triv; break;
  case "V_4": thisg=v4; break;
  case "S_3": thisg=s3; break;
  case "S_4": thisg=s4; break;
  case "Q_8": thisg=q8; break;
  case "GL_2(Z_2)": thisg=gl_2; break;
  case 'misc':
    var gtyp = getformvalue('groupname');
    if(gtyp == 'undefined') gtyp = myinp;
    
    switch (gtyp) {
    case "A_4": thisg=a4; break;
    case "SL_2(Z_3)": thisg=sl_3; break;
    case "heis3": thisg=heis3; break;
      /*    case "G_12": thisg=t12; break; */
    case "Z_3^2:Z_2": thisg=c3c3c2; break;
    default: thisg=cyclic(4); break;
    }
    break;
  default : thisg = triv;
    break;
  }
  if(typeof(thisg.mul) == 'undefined')
    return(new Group(thisg[0], thisg[1], thisg[2]));
  return(thisg);
}


/* Groups of order 16 */

d16 = new Array(1,2,3,4,5,6,7,8,
new Array(['<i>e</i>','<i>a</i>','<i>b</i>','<i>c</i>','<i>d</i>',
'<i>f</i>','<i>g</i>','<i>h</i>','<i>j</i>','<i>k</i>','<i>l</i>','<i>m</i>',
'<i>n</i>','<i>o</i>','<i>p</i>','<i>q</i>'], ['Z_2 . D_4'],
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
 [1, 4, 5, 6, 7, 9, 10, 0, 11, 12, 13, 14, 2, 3, 15, 8],
 [2, 11, 4, 8, 9, 6, 12, 15, 10, 0, 14, 7, 13, 5, 3, 1],
 [3, 13, 14, 4, 10, 11, 1, 6, 2, 8, 0, 12, 15, 7, 9, 5],
 [4, 7, 9, 10, 0, 12, 13, 1, 14, 2, 3, 15, 5, 6, 8, 11],
 [5, 14, 7, 11, 12, 10, 2, 8, 13, 1, 15, 0, 3, 9, 6, 4],
 [6, 3, 15, 7, 13, 14, 4, 10, 5, 11, 1, 2, 8, 0, 12, 9],
 [7, 0, 12, 13, 1, 2, 3, 4, 15, 5, 6, 8, 9, 10, 11, 14],
 [8, 5, 3, 9, 14, 7, 11, 12, 4, 10, 2, 13, 1, 15, 0, 6],
 [9, 15, 0, 14, 2, 13, 5, 11, 3, 4, 8, 1, 6, 12, 10, 7],
 [10, 6, 8, 0, 3, 15, 7, 13, 9, 14, 4, 5, 11, 1, 2, 12],
 [11, 9, 6, 12, 15, 0, 14, 2, 7, 13, 5, 3, 4, 8, 1, 10],
 [12, 8, 1, 15, 5, 3, 9, 14, 6, 7, 11, 4, 10, 2, 13, 0],
 [13, 10, 11, 1, 6, 8, 0, 3, 12, 15, 7, 9, 14, 4, 5, 2],
 [14, 12, 10, 2, 8, 1, 15, 5, 0, 3, 9, 6, 7, 11, 4, 13],
 [15, 2, 13, 5, 11, 4, 8, 9, 1, 6, 12, 10, 0, 14, 7, 3]]),
                10, 11, 12,13,14
                );


c3c3c2 = new Array(["(0,0;0)","(1,0;0)","(2,0;0)","(0,1;0)","(1,1;0)","(2,1;0)","(0,2;0)","(1,2;0)","(2,2;0)","(0,0;1)","(1,0;1)","(2,0;1)","(0,1;1)","(1,1;1)","(2,1;1)","(0,2;1)","(1,2;1)","(2,2;1)"],['<b>Z</b><sub>3</sub> x <b>Z</b><sub>3</sub> : <b>Z</b><sub>2</sub>','(+,+;+)'],
[[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17],
[1,2,0,4,5,3,7,8,6,10,11,9,13,14,12,16,17,15],
[2,0,1,5,3,4,8,6,7,11,9,10,14,12,13,17,15,16],
[3,4,5,6,7,8,0,1,2,12,13,14,15,16,17,9,10,11],
[4,5,3,7,8,6,1,2,0,13,14,12,16,17,15,10,11,9],
[5,3,4,8,6,7,2,0,1,14,12,13,17,15,16,11,9,10],
[6,7,8,0,1,2,3,4,5,15,16,17,9,10,11,12,13,14],
[7,8,6,1,2,0,4,5,3,16,17,15,10,11,9,13,14,12],
[8,6,7,2,0,1,5,3,4,17,15,16,11,9,10,14,12,13],
[9,11,10,15,17,16,12,14,13,0,2,1,6,8,7,3,5,4],
[10,9,11,16,15,17,13,12,14,1,0,2,7,6,8,4,3,5],
[11,10,9,17,16,15,14,13,12,2,1,0,8,7,6,5,4,3],
[12,14,13,9,11,10,15,17,16,3,5,4,0,2,1,6,8,7],
[13,12,14,10,9,11,16,15,17,4,3,5,1,0,2,7,6,8],
[14,13,12,11,10,9,17,16,15,5,4,3,2,1,0,8,7,6],
[15,17,16,12,14,13,9,11,10,6,8,7,3,5,4,0,2,1],
[16,15,17,13,12,14,10,9,11,7,6,8,4,3,5,1,0,2],
[17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0]]);

gl_2 = new Array(['<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>1<td>0<tr><td> 0<td>1</table></span>',
'<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>0<td>1<tr><td> 1<td>0</table></span>',
'<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>0<td>1<tr><td> 1<td>1</table></span>',
'<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>1<td>0<tr><td> 1<td>1</table></span>',
'<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>1<td>1<tr><td> 0<td>1</table></span>',
                  '<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>1<td>1<tr><td> 1<td>0</table></span>'], ['GL<sub>2</sub>(<b>Z</b><sub>2</sub>)', '&middot'],
[[0,1,2,3,4,5],
[1,0,4,5,2,3],
[2,3,5,4,1,0],
[3,2,1,0,5,4],
[4,5,3,2,0,1],
 [5,4,0,1,3,2]]);

sl_3 = new Array(['<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>1<td>0<tr><td> 0<td>1</table></span>',
'<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>0<td>1<tr><td> 2<td>0</table></span>',
'<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>0<td>1<tr><td> 2<td>1</table></span>',
'<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>0<td>1<tr><td> 2<td>2</table></span>',
'<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>0<td>2<tr><td> 1<td>0</table></span>',
'<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>0<td>2<tr><td> 1<td>1</table></span>',
'<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>0<td>2<tr><td> 1<td>2</table></span>',
'<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>1<td>0<tr><td> 1<td>1</table></span>',
'<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>1<td>0<tr><td> 2<td>1</table></span>',
'<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>1<td>1<tr><td> 0<td>1</table></span>',
'<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>1<td>1<tr><td> 1<td>2</table></span>',
'<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>1<td>1<tr><td> 2<td>0</table></span>',
'<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>1<td>2<tr><td> 0<td>1</table></span>',
'<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>1<td>2<tr><td> 1<td>0</table></span>',
'<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>1<td>2<tr><td> 2<td>2</table></span>',
'<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>2<td>0<tr><td> 0<td>2</table></span>',
'<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>2<td>0<tr><td> 1<td>2</table></span>',
'<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>2<td>0<tr><td> 2<td>2</table></span>',
'<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>2<td>1<tr><td> 0<td>2</table></span>',
'<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>2<td>1<tr><td> 1<td>1</table></span>',
'<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>2<td>1<tr><td> 2<td>0</table></span>',
'<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>2<td>2<tr><td> 0<td>2</table></span>',
'<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>2<td>2<tr><td> 1<td>0</table></span>',
'<span style="font-size: 65%"><table border="0"><tr><td rowspan="3" style="font-size: 250%; font-weight: 100">(<td><td><td rowspan="3" style="font-size: 250%; font-weight: 100">)<tr><td>2<td>2<tr><td> 2<td>1</table></span>'], ['SL<sub>2</sub>(<b>Z</b><sub>3</sub>)', '&middot', 0.4],
[[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23],
[1,15,18,21,0,9,12,11,20,3,14,17,2,8,23,4,13,22,6,10,16,5,7,19],
[2,17,20,23,7,10,13,9,19,1,12,16,3,0,22,6,14,21,5,11,15,4,8,18],
[3,16,19,22,8,11,14,10,18,2,13,15,1,7,21,5,12,23,4,9,17,6,0,20],
[4,0,12,9,15,21,18,22,13,5,19,7,6,16,10,1,20,11,2,23,8,3,17,14],
[5,8,14,11,16,22,19,23,12,6,20,0,4,17,9,3,18,10,1,21,7,2,15,13],
[6,7,13,10,17,23,20,21,14,4,18,8,5,15,11,2,19,9,3,22,0,1,16,12],
[7,2,3,1,6,4,5,8,0,10,11,9,13,14,12,17,15,16,20,18,19,23,21,22],
[8,3,1,2,5,6,4,0,7,11,9,10,14,12,13,16,17,15,19,20,18,22,23,21],
[9,20,23,17,13,7,10,19,2,12,16,1,0,22,3,21,6,14,15,5,11,18,4,8],
[10,19,22,16,14,8,11,18,3,13,15,2,7,21,1,23,5,12,17,4,9,20,6,0],
[11,18,21,15,12,0,9,20,1,14,17,3,8,23,2,22,4,13,16,6,10,19,5,7],
[12,11,8,14,22,19,16,5,23,0,6,20,9,4,17,18,10,3,21,7,1,15,13,2],
[13,9,0,12,21,18,15,4,22,7,5,19,10,6,16,20,11,1,23,8,2,17,14,3],
[14,10,7,13,23,20,17,6,21,8,4,18,11,5,15,19,9,2,22,0,3,16,12,1],
[15,4,6,5,1,3,2,17,16,21,23,22,18,20,19,0,8,7,12,14,13,9,11,10],
[16,5,4,6,3,2,1,15,17,22,21,23,19,18,20,8,7,0,14,13,12,11,10,9],
[17,6,5,4,2,1,3,16,15,23,22,21,20,19,18,7,0,8,13,12,14,10,9,11],
[18,22,16,19,11,14,8,3,10,15,2,13,21,1,7,12,23,5,9,17,4,0,20,6],
[19,23,17,20,10,13,7,2,9,16,1,12,22,3,0,14,21,6,11,15,5,8,18,4],
[20,21,15,18,9,12,0,1,11,17,3,14,23,2,8,13,22,4,10,16,6,7,19,5],
[21,13,10,7,20,17,23,14,6,18,8,4,15,11,5,9,2,19,0,3,22,12,1,16],
[22,12,9,0,18,15,21,13,4,19,7,5,16,10,6,11,1,20,8,2,23,14,3,17],
 [23,14,11,8,19,16,22,12,5,20,0,6,17,9,4,10,3,18,7,1,21,13,2,15]]);

heis3 = new Array([ '(0,0;0)', '(0,0;1)', '(0,0;2)', '(0,1;0)', '(0,1;1)',
'(0,1;2)', '(0,2;0)', '(0,2;1)', '(0,2;2)', '(1,0;0)', '(1,0;1)', '(1,0;2)',
'(1,1;0)', '(1,1;1)', '(1,1;2)', '(1,2;0)', '(1,2;1)', '(1,2;2)', '(2,0;0)',
'(2,0;1)', '(2,0;2)', '(2,1;0)', '(2,1;1)', '(2,1;2)', '(2,2;0)', '(2,2;1)',
'(2,2;2)' ], ['Z_3 x Z_3 : Z_3', '(+,+;+)'],
[[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26],
[1,2,0,4,5,3,7,8,6,10,11,9,13,14,12,16,17,15,19,20,18,22,23,21,25,26,24],
[2,0,1,5,3,4,8,6,7,11,9,10,14,12,13,17,15,16,20,18,19,23,21,22,26,24,25],
[3,4,5,6,7,8,0,1,2,12,13,14,15,16,17,9,10,11,21,22,23,24,25,26,18,19,20],
[4,5,3,7,8,6,1,2,0,13,14,12,16,17,15,10,11,9,22,23,21,25,26,24,19,20,18],
[5,3,4,8,6,7,2,0,1,14,12,13,17,15,16,11,9,10,23,21,22,26,24,25,20,18,19],
[6,7,8,0,1,2,3,4,5,15,16,17,9,10,11,12,13,14,24,25,26,18,19,20,21,22,23],
[7,8,6,1,2,0,4,5,3,16,17,15,10,11,9,13,14,12,25,26,24,19,20,18,22,23,21],
[8,6,7,2,0,1,5,3,4,17,15,16,11,9,10,14,12,13,26,24,25,20,18,19,23,21,22],
[9,13,17,12,16,11,15,10,14,18,22,26,21,25,20,24,19,23,0,4,8,3,7,2,6,1,5],
[10,14,15,13,17,9,16,11,12,19,23,24,22,26,18,25,20,21,1,5,6,4,8,0,7,2,3],
[11,12,16,14,15,10,17,9,13,20,21,25,23,24,19,26,18,22,2,3,7,5,6,1,8,0,4],
[12,16,11,15,10,14,9,13,17,21,25,20,24,19,23,18,22,26,3,7,2,6,1,5,0,4,8],
[13,17,9,16,11,12,10,14,15,22,26,18,25,20,21,19,23,24,4,8,0,7,2,3,1,5,6],
[14,15,10,17,9,13,11,12,16,23,24,19,26,18,22,20,21,25,5,6,1,8,0,4,2,3,7],
[15,10,14,9,13,17,12,16,11,24,19,23,18,22,26,21,25,20,6,1,5,0,4,8,3,7,2],
[16,11,12,10,14,15,13,17,9,25,20,21,19,23,24,22,26,18,7,2,3,1,5,6,4,8,0],
[17,9,13,11,12,16,14,15,10,26,18,22,20,21,25,23,24,19,8,0,4,2,3,7,5,6,1],
[18,25,23,21,19,26,24,22,20,0,7,5,3,1,8,6,4,2,9,16,14,12,10,17,15,13,11],
[19,26,21,22,20,24,25,23,18,1,8,3,4,2,6,7,5,0,10,17,12,13,11,15,16,14,9],
[20,24,22,23,18,25,26,21,19,2,6,4,5,0,7,8,3,1,11,15,13,14,9,16,17,12,10],
[21,19,26,24,22,20,18,25,23,3,1,8,6,4,2,0,7,5,12,10,17,15,13,11,9,16,14],
[22,20,24,25,23,18,19,26,21,4,2,6,7,5,0,1,8,3,13,11,15,16,14,9,10,17,12],
[23,18,25,26,21,19,20,24,22,5,0,7,8,3,1,2,6,4,14,9,16,17,12,10,11,15,13],
[24,22,20,18,25,23,21,19,26,6,4,2,0,7,5,3,1,8,15,13,11,9,16,14,12,10,17],
[25,23,18,19,26,21,22,20,24,7,5,0,1,8,3,4,2,6,16,14,9,10,17,12,13,11,15],
[26,21,19,20,24,22,23,18,25,8,3,1,2,6,4,5,0,7,17,12,10,11,15,13,14,9,16]]);
