# Copyright (c) 2018-2021, Carnegie Mellon University
# See LICENSE for details

processCRP := meth(partA, partB)
    local size, switches, next, modA, modB, loc, setA, choice;
    size := Length(partA);

    modA := List([1..size], i-> Mod(partA[i], size));
    modB := List([1..size], i-> Mod(partB[i], size));

    setA := [];

    switches := List([1..size], i->-1);
    next := -1;

    while (-1 in switches) do

        # if next = -1, then we can make an arbitrary decision.
        if (next < 0) then
            # find the next switch that isn't set.
            choice := PositionProperty(modA, i-> i <> -1);

            switches[choice] := 0;
            Append(setA, [modA[choice]]);

            # We just assigned modA[choice] to partition A.
            # Thus, we precluded modB[choice] from being in parition A.
            # So, we need to check.  If modB[choice] is not already in
            # partition A, then we need to make it our next choice.

            if (modB[choice] in setA) then
                next := -1;
            else
                next := modB[choice];
            fi;
            modA[choice] := -1; modB[choice] := -1;

        else
            # if next >= 0, then it represents the 'mod' number we must
            # assign to partition A.

            # the next value must be in the set of unchosen values in one
            # of the two partitions.
            if (next in modA) then
                choice := Position(modA, next);
                switches[choice] := 0;
                Append(setA, [modA[choice]]);

                if (modB[choice] in setA) then
                    next := -1;
                else
                    next := modB[choice];
                fi;

            else
                choice := Position(modB, next);
                switches[choice] := 1;
                Append(setA, [modB[choice]]);

                if (modA[choice] in setA) then
                    next := -1;
                else
                    next := modA[choice];
                fi;
            fi;
            modA[choice] := -1; modB[choice] := -1;
        fi;

    od;

    return switches;
end;

# Given a permutation permIn, determine the switch settings for the
# un-simplified \Omega\Omega^{-1} network.
#
# See: K. Y. Lee.  On the rearrangeability of 2(\log_2n)-1 stage
# permutation networks.  IEEE Transactions on Computers,
# C-34(5):412-425, May 1985.

# We do not simplify the network, because my network generation code
# will simplify all unneeded switches.

routePerm := meth(permIn)
    local n, ss, ns, p, p2, col, numP, partSize, partA, partB, i, j, k, partStride, tmpSw, swCol, tmp;

#    Print("Routing perm: ", permIn, "\n");

    n := Length(permIn);

    # The perm comes in as row values.  I.e., PermRowVals[i] -> i.
    # We need to transpose this.
    p := List([1..n], i->0);
    for i in [0..n-1] do p[permIn[i+1]+1] := i; od;

    # This is suboptimal.  For now, I will simply let the unnecessary
    # switches be all-zero.
    ns := n/2 * (LogInt(n,2) + LogInt(n,2)-1);

    ss := [];

    for k in [0..LogInt(n,2)-2] do
        col := LogInt(n,2)-1-k;

        # Permute output values by L(n,n/2)
        p2 := List([1..n], i -> p[L(n,n/2).at(i-1).ev()+1]);

        # Now we need to set the switches for the E^{col}_{R} column.
        numP := 2^k;
        partSize := n/(2*numP);
        partStride := 2*numP;

        swCol := List([1..n/2], i -> -1);
        tmpSw := [];
        for i in [0..numP-1] do
            partA := [];
            partB := [];
            for j in [1..partSize] do
                Append(partA, [p2[partStride*(j-1)+1+(2*i)]]);
                Append(partB, [p2[partStride*(j-1)+2+(2*i)]]);
            od;

            Append(tmpSw, processCRP(partA, partB));

        od;

        swCol := List([1..n/2], i -> tmpSw[L(n/2,n/(2^(k+1))).at(i-1).ev()+1]);
        Append(ss, swCol);

        # Now we have to perform the permutation based on the
        # switches we set.
        for i in [0..n/2-1] do
            if (swCol[i+1] = 1) then
                p[i*2+1] := p2[i*2+2];
                p[i*2+2] := p2[i*2+1];
            else
                p[i*2+1] := p2[i*2+1];
                p[i*2+2] := p2[i*2+2];
            fi;
        od;
    od;

    # Permute output values by L(n,n/2)
    p2 := List([1..n], i -> p[L(n,n/2).at(i-1).ev()+1]);

    for k in [0..LogInt(n,2)-1] do

        swCol := [];
        # go through the n/2 switches.  at each switch, if the top input's value
        # has a 1 in bit k.
        for i in [0..n/2-1] do
            if (imod(idiv(p2[2*i+1], 2^k), 2).ev() = 1) then
                Append(swCol, [1]);
            else
                Append(swCol, [0]);
            fi;
        od;

        Append(ss, swCol);

        # Now we have to perform the permutation based on the
        # switches we set.
        for i in [0..n/2-1] do
            if (swCol[i+1] = 1) then
                p[i*2+1] := p2[i*2+2];
                p[i*2+2] := p2[i*2+1];
            else
                p[i*2+1] := p2[i*2+1];
                p[i*2+2] := p2[i*2+2];
            fi;
        od;

        # Permute output values by L(n,2)
        p2 := List([1..n], i -> p[L(n,2).at(i-1).ev()+1]);

    od;

    p := List([0..n-1], i->i);
    if (p <> p2) then
        Error("Failed at configuring switching network.");
    fi;

#    Print("Configuration: ", ss, "\n");

    return ss;
end;

# Given a permutation permIn, determine the switch settings for the
# \Omega^{-1} network.
#
# We will return -1 if the permutation cannot be mapped to that network.
# In this case, then, you should use routePerm to map it to the
# \Omega\Omega^{-1} network.

# For now, we will cheat and use the \Omega\Omega^{-1} routing algorithm.
# It appears that when we use this algorithm on a permutation that can be
# permformed on the \Omega^{-1}, then the configuration bits are simply
# all zero for the first \Omega portion of the network.  However, I haven't
# verified this and I haven't looked at the routePerm code in many years.

# I have not proven this will be the case, but it seems to work on the
# circular shift permutations I am currently using.

# All circular shifts will be Omega^{-1} passable by Theorems 3  and 13 of "Access
# and Alignment of Data in an Array Processor," Duncan H. Lawrie, IEEE Tr.
# on Computers, C-24 (12), 1975.
#
# Theorem 3: Duncan says that if you can represent your mapping as:
# P_N(a, b, c, d, e) = {(ax+b, cx+d) | 0 \leq x < e}
# where ax+b represents the source (mod N) and cx+d represents the
# destination (mod N),
# then your mapping is \Omega passable if gcd(a,N) \leq gcd(c,N) and
# e \leq N/(gcd(c,N).
# For a cyclic shift, a and c will always = 1, b=0, d equals the number
# of positions to shift, and e=N.
# Since a and c are 1, gcd(a,N) and gcd(c,N) are both 1 and hence equal.
# Further, N/gcd(c,N) = N, and therefore e = N/gcd(c,N).

# Lastly we note that Theorem 13 says that if a mapping is \Omega passable
# it must be \Omega^{-1} passable.

routePermOmegaInv := meth(permIn)
    local full, n, ns, numSwitchesZero;

    full := routePerm(permIn);
    n := Length(permIn);
    ns := n/2 * (LogInt(n,2) + LogInt(n,2)-1);

    # If this permutation is Omega^{-1} routable, then the first numSwitchesZero
    # must be configured to zero.
    numSwitchesZero := n/2 * (LogInt(n,2)-1);

    if (Sum(full{[1..numSwitchesZero]}) <> 0) then
	return -1;
    fi;

    return full{[numSwitchesZero+1 .. ns]};
end;

cyclicShiftList := meth(size, shift)
    local t, res, t2;
    t := [0..size-1];
    shift := Mod(shift, size);
    res := t{[shift+1..size]};
    if (shift = 1) then
	t2 := [0];
    else
	t2 := t{[1..shift]};
    fi;

    Append(res, t2);
    return res;
end;

Class(omegaInvNetwork, BaseMat, SumsBase, rec(

    abbrevs := [(n, p, it) -> [n, p, it]],

    new := (self, n, perms) >> SPL(WithBases(self, rec(
		    dimensions := [n, n],
		    _children := [n, perms]
	     ))),
    rChildren := self >> self._children,
    rSetChild := meth(self, n, what) self._children[n] := what; end,
    child     := (self, n) >> self._children[n],
    children  := self >> self._children,

    print      := (self,i,is) >> Print(self.name, "(", self.child(1), ", ", self.child(2), ")"),

    dims := self >> [self._children[1], self._children[1]],

));

overlapAddCyclicShifts := meth(num_points, step_size, num_steps)
    local res, i, it;

    res := [];
    for i in [0 .. num_steps-1] do
       Append(res, [[routePermOmegaInv(cyclicShiftList(num_points, step_size*i))]]);
    od;

    return omegaInvNetwork(num_points, res);

end;

# generage Verilog for Inverse Omega network for a family of cyclic shift permutation,
# given by C_n^(s*l), where l = 0 .. k.
# In code below, n is "num_points", s is "step_size", k is "num_steps".
#
# "bits" gives the number of bits per word.
# "name" gives the output filename, which will be created in /tmp/spiral/[PID] where
# [PID] is Spiral's process ID.  You can find the current PID in Spiral by typing
# getPid();
#
# If you want to change this diretory, it is easy to do
# so; just edit the file hacks.gi in spiral/paradigms/stream/hacks.gi
#
# To generate an 8 point network that will perform C_8^0, C_8^1, ..., C_8^7, and
# store it in file.v, run:
#    genOverlapAddShift(8, 1, 8, 16, "file");
genOverlapAddShift := function(num_points, step_size, num_steps, bits, name)
    local opts;
    opts := InitStreamUnrollHw();
    HDLGen(overlapAddCyclicShifts(num_points, step_size, num_steps), 1, 0, bits, 0, 0, name);
end;


IsKaprekar := function(n)
	local a, b, p, q;
	if n = 1 then
		return true;
	fi;
	q := n*n;
	p := 10;
	while p < q do
		a := RemInt(q, p);
		b := QuoInt(q, p);
		if a > 0 and a + b = n then
			return true;
		fi;
		p := p*10;
	od;
	return false;
end;

Filtered([1 .. 10000], IsKaprekar);
# [ 1, 9, 45, 55, 99, 297, 703, 999, 2223, 2728, 4879, 4950, 5050, 5292, 7272,
#   7777, 9999 ]

Size(last);
# 17

Filtered([1 .. 1000000], IsKaprekar);
# [ 1, 9, 45, 55, 99, 297, 703, 999, 2223, 2728, 4879, 4950, 5050, 5292, 7272,
#   7777, 9999, 17344, 22222, 38962, 77778, 82656, 95121, 99999, 142857,
#   148149, 181819, 187110, 208495, 318682, 329967, 351352, 356643, 390313,
#   461539, 466830, 499500, 500500, 533170, 538461, 609687, 627615, 643357,
#   648648, 670033, 681318, 791505, 812890, 818181, 851851, 857143, 961038,
#   994708, 999999 ]

Size(last);
# 54

IsKaprekarAndHow := function(n, base)
	local a, b, p, q;
	if n = 1 then
		return true;
	fi;
	q := n*n;
	p := base;
	while p < q do
		a := RemInt(q, p);
		b := QuoInt(q, p);
		if a > 0 and a + b = n then
			return [a, b];
		fi;
		p := p*base;
	od;
	return false;
end;

IntegerToBaseRep := function(n, base)
	local s, digit;
	if base > 36 then
		return fail;
	elif n = 0 then
		return "0";
	else
		s := "";
		digit := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
		while n <> 0 do
			Add(s, digit[RemInt(n, base) + 1]);
			n := QuoInt(n, base);
		od;
		return Reversed(s);
	fi;
end;

PrintIfKaprekar := function(n, base)
	local v;
	v := IsKaprekarAndHow(n, base);
	if IsList(v) then
		Print(n, "(10) or in base ", base, ", ",
			IntegerToBaseRep(n, base), "^2 = ",
			IntegerToBaseRep(n^2, base), " and ",
			IntegerToBaseRep(v[2], base), " + ",
			IntegerToBaseRep(v[1], base), " = ",
			IntegerToBaseRep(n, base), "\n");
	fi;
	return fail;
end;

# In base 17...
Perform([1 .. 1000000], n -> PrintIfKaprekar(n, 17));
# 16(10) or in base 17, G^2 = F1 and F + 1 = G
# 64(10) or in base 17, 3D^2 = E2G and E + 2G = 3D
# 225(10) or in base 17, D4^2 = A52G and A5 + 2G = D4
# 288(10) or in base 17, GG^2 = GF01 and GF + 1 = GG
# 1536(10) or in base 17, 556^2 = 1B43B2 and 1B4 + 3B2 = 556
# 3377(10) or in base 17, BBB^2 = 8093B2 and 809 + 3B2 = BBB
# 4912(10) or in base 17, GGG^2 = GGF001 and GGF + 1 = GGG
# 7425(10) or in base 17, 18BD^2 = 24E166G and 24E + 166G = 18BD
# 9280(10) or in base 17, 1F1F^2 = 39B1B94 and 39B + 1B94 = 1F1F
# 16705(10) or in base 17, 36DB^2 = B992C42 and B99 + 2C42 = 36DB
# 20736(10) or in base 17, 43CD^2 = 10DE32FG and 10DE + 32FG = 43CD
# 30016(10) or in base 17, 61EB^2 = 23593F92 and 2359 + 3F92 = 61EB
# 36801(10) or in base 17, 785D^2 = 351E433G and 351E + 433G = 785D
# 37440(10) or in base 17, 7A96^2 = 37144382 and 3714 + 4382 = 7A96
# 46081(10) or in base 17, 967B^2 = 52G94382 and 52G9 + 4382 = 967B
# 46720(10) or in base 17, 98B4^2 = 5575433G and 5575 + 433G = 98B4
# 53505(10) or in base 17, AF26^2 = 6GA43F92 and 6GA4 + 3F92 = AF26
# 62785(10) or in base 17, CD44^2 = 9A5532FG and 9A55 + 32FG = CD44
# 66816(10) or in base 17, DA36^2 = AEG42C42 and AEG4 + 2C42 = DA36
# 74241(10) or in base 17, F1F2^2 = D75F1B94 and D75F + 1B94 = F1F2
# 76096(10) or in base 17, F854^2 = E1F5166G and E1F5 + 166G = F854
# 83520(10) or in base 17, GGGG^2 = GGGF0001 and GGGF + 1 = GGGG
# 266224(10) or in base 17, 33334^2 = A2C52A07G and A2C5 + 2A07G = 33334


# Creating an array of 0
a := NullMat(2, 2);
# [ [ 0, 0 ], [ 0, 0 ] ]

# Some assignments
a[1][1] := 4;
a[1][2] := 5;
a[2][1] := 3;
a[2][2] := 4;

a
# [ [ 4, 5 ], [ 3, 4 ] ]

Determinant(a);
# 1


# Copyright (c) 2018-2020, Carnegie Mellon University
# See LICENSE for details
#
# Quantum Hadamard transform
#

#F qHT( n ) - Hadamard Gate non-terminal
#F Definition: (2^n x 2^n)-matrix  that applies an n-point Hadamard Transform to the target qubits
#F Note:       qHT(1) denotes the matrix 1/sqrt(2)* [[1, 1], [1, -1]],
#F             qHT(_) is symmetric.
#F
#F qHT( n ) -> an n-qubit Hadamard transform
Class(qHT, TaggedNonTerminal, rec(
  abbrevs   := [ n -> Checked(IsPosInt(n), [n]) ],
  dims      := self >> let(size := 2^self.params[1], [size, size]),
  terminate := self >> Tensor(Replicate(self.params[1], qH())),
  isReal    := self >> true,
  groups    := self >> [Replicate(self.params[1], 1)],
  connected := self >> false,
  rChildren := self >> self.params,
  from_rChildren := (self, rch) >> self.__bases__[1](rch[1]),
  recursive_def := (self, arch) >> self.__bases__[1](self.params[1]),
  SmallRandom := () -> Random([2..5]),
  LargeRandom := () -> Random([6..15]),
  normalizedArithCost := self >> Error("ArithCost not implemented"),
  TType := T_Real(64)
));

##
#F qHT Breakdown Rules
##
NewRulesFor(qHT, rec(

    # qHT_BinSplit rule
    # qHT_BinSplit qHT_(k) -> (qHT_(k1) tensor I) (I tensor qHT_(k2))
    # k1 + k2 = k
    qHT_BinSplit := rec (
        forTransposition := false,
        minSize          := 2,
        applicable       := (self, nt) >> nt.params[1] > 1,
        children         := nt -> List( [1..nt.params[1] - 1],  i -> [ Tensor(qHT(i), qHT(nt.params[1]-i)).withTags(nt.getTags()) ]  ),
        apply            := (nt, c, cnt) -> c[1],
        switch := true,
    ),

    #F qHT_Base: qHT(1) = qH() SPL object
    #F Directly represent as an implementable gate
    qHT_Base := rec(
        info             := "qHT_(1) -> qH()",
        forTransposition := false,
        applicable       := (self, nt) >> nt.params[1]=1,
        apply            := (nt, c, cnt) -> qH(),
    )

));


LookAndSay := function(s)
  local c, r, cur, ncur, v;
  v := "123";
  r := "";
  cur := 0;
  ncur := 0;
  for c in s do
    if c = cur then
      ncur := ncur + 1;
    else
      if ncur > 0 then
        Add(r, v[ncur]);
        Add(r, cur);
      fi;
      cur := c;
      ncur := 1;
    fi;
  od;
  Add(r, v[ncur]);
  Add(r, cur);
  return r;
end;

LookAndSay("1");     # "11"
LookAndSay(last);    # "21"
LookAndSay(last);    # "1211"
LookAndSay(last);    # "111221"
LookAndSay(last);    # "312211"
LookAndSay(last);    # "13112221"
LookAndSay(last);    # "1113213211"
LookAndSay(last);    # "31131211131221"
LookAndSay(last);    # "13211311123113112211"
LookAndSay(last);    # "11131221133112132113212221"
LookAndSay(last);    # "3113112221232112111312211312113211"
LookAndSay(last);    # "1321132132111213122112311311222113111221131221"
LookAndSay(last);    # "11131221131211131231121113112221121321132132211331222113112211"
LookAndSay(last);    # "311311222113111231131112132112311321322112111312211312111322212311322113212221"


Find := function(v, x)
  local low, high, mid;
  low := 1;
  high := Length(v);
  while low <= high do
    mid := QuoInt(low + high, 2);
    if v[mid] > x then
      high := mid - 1;
    elif v[mid] < x then
      low := mid + 1;
    else
      return mid;
    fi;
  od;
  return fail;
end;

u := [1..10]*7;
# [ 7, 14, 21, 28, 35, 42, 49, 56, 63, 70 ]
Find(u, 34);
# fail
Find(u, 35);
# 5


# Copyright (c) 2018-2021, Carnegie Mellon University
# See LICENSE for details

Declare(CodeBlock, BRAMPermStreamOne, TPrmMulti);

Class(TPrmMulti, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (tspl_list, which) -> [tspl_list, which] ],
    dims := self >> Cond(IsBound(self.params[1][1].domain), [self.params[1][1].domain(), self.params[1][1].range()], self.params[1][1].dims()),

    terminate := meth(self)
       local prms, res, len, p, i;
       prms := self.params[1];
       len := Length(prms);
       res := prms[len];
       for i in Reversed([1..len-1]) do
          p := Cond(IsBound(prms[i].terminate), prms[i].terminate(), Prm(prms[i]));
          res := COND(eq(self.params[2], (i-1)), p, res);
       od;

       return res.terminate();
    end,

    transpose := self >> TPrmMulti(List(self.params[1], i->i.transpose()), self.params[2]).withTags(self.getTags()),

    isReal := True,
    doNotMeasure := true,
    noCodelet := true,
    normalizedArithCost := self >> 0
));

#F PermutationModMatrix ( <perm>, <n>, <m> )
#F   constructs for the permutation <perm> on <n> points an
#F   <m> x <m> matrix M. The (i,j) entry of M contains the number
#F   of pairs (k, k^<perm>) with k mod m = i and k^{perm> mod m = j.
#F   <m> has to divide <n>.
#F   We view <perm> as a permutation of the points {0,.., n-1} even
#F   though in gap it is represented as permutation on {1,..,n}
#F   (i.e shifted by 1).
#F

PermutationModMatrix := function ( p, n, m)
  local M, i;

  # error checking
  if not IsPerm(p) then
    Error("<p> is not a permutation");
  fi;
  if not ( IsInt(n) and IsInt(m) ) then
    Error("<n> and <m> must be integers");
  fi;
  if not n mod m = 0 then
    Error("<m> must divide <n>");
  fi;
  if not ( p = () or LargestMovedPointPerm(p) <= n ) then
    Error("<n> is not a valid degree for <perm>");
  fi;

  # initialize m x m matrix M
  M := List([1..m], r -> List([1..m], c -> 0));

  # construct M
  for i in [1..n] do
    M[(i-1) mod m + 1][(i^p-1) mod m + 1] :=
      M[(i-1) mod m + 1][(i^p-1) mod m + 1] + 1;
  od;

  return M;
end;

## ============================================================
##
##  PermMatrixRowVals(p) takes permutation matrix p and returns a list
##    of the '1' location in each row of the matrix.
##
##  Thus, the value a in list element b means that the input in
##    location b will be permuted to location a in the output.
##
## ============================================================
PermMatrixRowVals := meth(x)

   return List(Prm(FPerm(x)).func.lambda().tolist(), i->i.ev());
end;

## ============================================================
##
##  PermMatrixRowVals(p) takes permutation matrix p and returns a list
##    of the '1' location in each row of the matrix.
##
##  Thus, the value a in list element b means that the input in
##    location b will be permuted to location a in the output.
##
## ============================================================
PermMatrixRowValsOld := meth(x)
   local length, i, res;

   res := [];
   length := Length(x);
   for i in [1 .. length] do
      res[i] := BasisVecToNum(x[i]);
   od;
   return res;
end;

## ============================================================
##
##  min_list(l) returns the smallest value in l, a list of lists.
##
## ============================================================
min_list := meth(l)
   local t, i;
   t := [];
   for i in [1 .. Length(l)] do
      t[i] := Minimum(l[i]);
   od;
   return Minimum(t);
end;

## ============================================================
##
##  max_list(l) returns the largest value in l, a list of lists.
##
## ============================================================
max_list := meth(l)
   local t, i;
   t := [];
   for i in [1 .. Length(l)] do
      t[i] := Maximum(l[i]);
   od;
   return Maximum(t);
end;

## ============================================================
##
##  RandomPerm(n) returns a random permutation on n points.
##
##  RandomPermSPL(n) is not used becuase 1/3 of the time it
##  returns a stride permutation, and 1/3 of the time it
##  returns J(n).
##
## ============================================================
RandomPerm := meth(n)
   return Perm(Random(SymmetricGroup(n)), n);
end;

## ============================================================
##
##   IsPermMult(p) returns 1 if matrix p is a permutation of a
##   constant multiple of a permutation, and 0 otherwise.
##
## ============================================================

IsPermMult := meth(p)
   local n, i, j, total;
   n := Length(p);
   for i in [1..n] do
      total := 0;
      for j in [1..n] do
         if (p[i][j] <> 0) then
            total := total+1;
	    if (total > 1) then
		return 0;
	    fi;
         fi;
      od;
   od;
   return 1;
end;

## ============================================================
##   OnesVector(n)
##   Returns a vector of 1s of length n
## ============================================================
OnesVector := meth(n)
   local res, i;
   res := [];
   for i in [1..n] do
      res[i] := 1;
   od;
   return res;
end;

## ============================================================
##  Given the mux addresses, i.e., the multiplexor settings
##  that say "given an output location, where does it get its
##  input from on each cycle," determine the inverse, i.e.,
##  "given an input location, determine where its output will
##  go for each cycle.
##
##  This function is intended as a helper function, used in
##  StreamPermGen.createCode().
## ============================================================
PermHelperFindPortAddresses := meth(muxaddr)
    local ports, i, row, j, rowres;
    ports := [];
    for i in [1..Length(muxaddr)] do
       rowres := [];
       row := muxaddr[i];
       for j in [1..Length(row)] do
          rowres[row[j]+1] := j-1;
       od;
       Append(ports, [rowres]);
    od;
    return ports;
end;

_optsInColumn := meth(col, rows_left)
    local res, i, size;
    size := Length(col);
    res := [];
    for i in [1..size] do
        if ((rows_left[i] = 1) and (col[i] > 0)) then
            Append(res, [i]);
        fi;
    od;
    return res;
end;

# Used in debugging.
#_findPermBacktracks := 0;

try_column := meth(whichCol, board, rows_left)
    local size, col, options, i, spec_rows_left, tres;

    size := Length(board[1]);

    col := TransposedMat(board)[whichCol];

    options := _optsInColumn(col, rows_left);

    for i in options do
        if (whichCol = size) then
            return [i];
        fi;

        rows_left[i] := 0;
        tres := try_column(whichCol+1, board, rows_left);
        if (tres <> [-1]) then
            Append(tres, [i]); # backwards
            return tres;
        fi;
        rows_left[i] := 1;
    od;

    # Debugging
    #_findPermBacktracks := _findPermBacktracks+1;
    return [-1];

end;

_randomPermGood := meth(board)
    local rows_left, permVec, size, permMat, col, row, choices, failed, choice, i;
    size      := Length(board[1]);
    rows_left := List([1..size], i->1);
    permVec := [];

    failed := false;
    col := 1;
    while ((col <= size) and (not failed)) do

        # Collect list of choices in this column.
        choices := [];
        for row in [1..size] do
            if ((rows_left[row] <> 0) and (board[row][col] > 0)) then
                Append(choices, [row]);
            fi;
        od;

        if (Length(choices) = 0) then
            failed := true;
        else
            choice := Random(choices);
            rows_left[choice] := 0;
            Append(permVec, [choice]);
        fi;

        col := col + 1;
    od;

    if (failed) then
        # If we fail, then we can assume that the square contains at least one 0 entry.  So,
        # we can send back the all 1 matrix, and be guaranteed that P - [1] < 0 for some value.
        return(List([1..size], r -> List([1..size], c -> 1)));
    else
        permMat   := [];
        for i in [1..size] do
            permMat[i] := BasisVec(size, permVec[i]-1);
        od;

        permMat := TransposedMat(permMat);

        return permMat;
    fi;
end;

_findPerm := meth(board)
    local rows_left, permVec, size, permMat, i;
    size      := Length(board[1]);
    rows_left := List([1..size], i->1);
    permVec   := Reversed(try_column(1, board, rows_left));
    permMat   := [];

    for i in [1..size] do
        permMat[i] := BasisVec(size, permVec[i]-1);
    od;

    permMat := TransposedMat(permMat);

    return permMat;
end;

_minisat_path := "";

_findPermUsingSat := meth(board)
    local size, r, c, zeros, dir, i, zerofile, outfile, res, permmat;

    size := Length(board[1]);
    zeros := [];

    for r in [1..size] do
       for c in [1..size] do
           if (board[r][c] = 0) then
               Append(zeros, [ (r-1)*size + c ]);
           fi;
       od;
    od;

    dir := Concat("/tmp/spiral/", String(GetPid()));
    MakeDir(dir);
    zerofile := Concat(dir, "/zeros");
    outfile := Concat(dir, "/out");
    PrintTo(zerofile, "");

    for i in [1..Length(zeros)] do
        AppendTo(zerofile, zeros[i], "\n");
    od;

    if (_minisat_path = "") then
        Error("Error: path to SAT solver not set: paradigms.stream._minisat_path is not bound.");
    fi;

#    IntExec(Concat("/Users/pam/minisat/core/minisat ", String(size), " ", zerofile, " ", outfile));
    IntExec(Concat(_minisat_path, " ", String(size), " ", zerofile, " ", outfile));

    res := ReadVal(outfile);

    permmat := [];
    for i in [1..size] do
       permmat[i] := BasisVec(size, res[i]);
    od;

    return permmat;
end;

_SumList := meth(m)
    local res, i;
    res := 0;
    for i in [1..Length(m)] do
        res := res + m[i];
    od;
    return res;
end;

_whichMeth := 1;

Declare(routePerm);

## ============================================================
##     listofperms = FactorSemiMagicSquare(square)
##
##  Given a semi-magic square of size n, find a (non-unique)
##  decomposition into a sum of permutations.  Returns the
##  decomposition as a list of permutations (with each perm
##  being represented in the "row value" form).
##
##  Each semi-magic square can be decomposed into at most
##  (n-1)^2 + 1 different permutations.  See:
##      Leep et al. Marriage, Magic, and Solitaire. The American
##      Mathematical Monthly (1999) vol. 106 (5) pp. 419-429.
##
##  This method uses randomly generated permutations.  A better
##  method would construct them based upon the square.  A more
##  realistic approach would be a hybrid of the two.
##
##  This function is intended as a helper function, used in
##  StreamPermGen.createCode().
## ============================================================
FactorSemiMagicSquare := meth(square)
   local length, multplr, rndprm, j, res, rows_left, cols_left, startTime, endTime, tries, which, ct;
   length := Length(square);
   res := [];

   # Debugging
   startTime := TimeInSecs();
   tries := 0;

   ct := 0;

   which := stream._whichMeth;

   ##  While the semi-magic square is not all zero...
   while (max_list(square) > 0) do

	  ## What is left?  Is it a multiple of a permutation matrix?
	  ## (This step not needed for correctness, but is a performance
	  ## optimization.)
	  if (IsPermMult(square) = 1) then

	      ## If what is left is a constant times a perm matrix,
	      ## set that perm as the random step, and complete the
	      ## final step.
	      rndprm := square / max_list(square);
	      multplr := 0;
	      while (min_list(square - multplr*rndprm) >= 0) do
	         multplr := multplr+1;
	      od;
	      multplr := multplr-1;
	      square := square - multplr*rndprm;
	      for j in [1..multplr] do
#! fails sometimes	         Append(res, [routePerm(PermMatrixRowValsOld(rndprm))]);
	         Append(res, [PermMatrixRowValsOld(rndprm)]);
	      od;

          ct := ct + 1;

      else

          ##  Generate a random permutation.
          # set 'which' as: paradigms.stream.whichMeth := 0;

#          rndprm := _randomPermGood(square);
          tries := tries+1;

#            if (which = 0) then
#                rndprm := MatSPL(RandomPerm(length));
#                #tries := tries + 1;
#            else
#                if (which = 1) then
#                    rndprm := _findPerm(square);
#                else
                    rndprm := _findPermUsingSat(square);
#                fi;
# 	       fi;
      fi;

      ##  How many times can this permutation be
      ##  subtracted from the square?
      multplr := 0;
      while (min_list(square - multplr*rndprm) >= 0) do
         multplr := multplr+1;
      od;
      multplr := multplr-1;

      ## If this permutation has been subtracted from the square,
      ## record the decision, and check if we are done.
      if (multplr > 0) then

          ## Subtract the permutation the correct number of times.
          square := square - multplr*rndprm;

     	  ## Record the decision
	      for j in [1..multplr] do
	          Append(res, [PermMatrixRowValsOld(rndprm)]);
	      od;

          endTime := TimeInSecs();
#          Print(which, ", ", (endTime - startTime), ", ", tries, "\n");
          startTime := endTime;
          tries := 0;
          ct := ct + 1;

      fi;
   od;

   return res;
end;

## ============================================================
##  row = PermHelperFindRow(permrows, x (readdport),
##                           y (writeport), streaming-width)
##
##  Given a permutation as a set of row values, and a selected
##  read and write port (x, y), find which permutation word can
##  be scheduled that will lead to a read from port x to a
##  write in port y.
##
##  As the algorithm progresses, words that have been scheduled
##  are removed from permrows and replaced with -1.
##
##  This function is intended as a helper function, used in
##  StreamPermGen.createCode().
## ============================================================
PermHelperFindRow := meth(p, x, y, w)
   local a, b;

   for b in [1 .. Length(p)] do
      a := p[b];

      # The a <> -1 isn't needed for correctness but it makes the
      # code more clear.
      if ((a <> -1) and (Mod(a,w) = x) and (Mod(b-1,w) = y)) then
         return b;
      fi;
   od;

   Error("Cannot find a word to schedule.");
end;

## ============================================================
##  [rd_addr, wr_addr] = PermHelperFindAddresses(permrows,
##                          muxaddr, w);
##
##  Given a permutation as a set of row values and a cycle-by-
##  cycle read/write port mapping (muxaddr), and streaming width
##  w, determine the assoicated memory read and write addresses.
##
##  This function is intended as a helper function, used in
##  StreamPermGen.createCode().
## ============================================================
PermHelperFindAddresses := meth(permrows, muxaddr, w)
   local rd_addr, wr_addr, i, x, y, b, a, rdcyc, wrcyc, wrcyc2, ports;

   ports := PermHelperFindPortAddresses(muxaddr);
   rd_addr := []; wr_addr := [];

   for i in [1..Length(permrows)/w] do
      rdcyc := []; wrcyc := []; wrcyc2 := [];

      for x in [1..w] do
         y := ports[i][x]+1;
	 b := PermHelperFindRow(permrows, x-1, y-1, w);
	 a := permrows[b];
	 Append(rdcyc, [floor(a/w).v]);
	 Append(wrcyc, [floor((b-1)/w).v]);
	 Append(wrcyc2, []);
	 permrows[b] := -1;
      od;
      Append(rd_addr, [rdcyc]);

      # Here, wrcyc tells the write address for each *read port* at this
      # time.  I need to permute these values so it tells the write address
      # for each *write port* at this time.
      # So, I just need to assign wrcyc to wrcyc2 based upon this mapping.
      for x in [1..w] do
         wrcyc2[x] := wrcyc[muxaddr[i][x]+1];
      od;

      Append(wr_addr, [wrcyc2]);
   od;
   return [rd_addr, wr_addr];
end;

## ==================================================================
##  BRAMPermGeneral([rdaddr], [swnetctrl], [wraddr], [perm], it)
##    where rdaddr is the set of read addresses, swnetctrl is the
##    control bits for the \Omega^{-1}\Omega network, wraddr is the
##    set of write addresses, and perm is the permutation as an SPL
##    object.  it is the iteration variable that specifies which perm
##    to perform.
##
##  This represents a streaming multi-permutation structure that has
##  been factored using our "general" method.
##
##  This is not ideal for permutations that are linear on the bits.
##
##  Do not attempt to build this object by hand. Instead use
##  StreamPerm(perm, streamsize) to build a generic streaming
##  perm.  Then, StreamPerm.createCode() will construct the
##  BRAMPermGeneral given the permutation and streaming width.
## ==================================================================
Class(BRAMPermGeneral, BaseMat, SumsBase, rec(
	abbrevs := [(r,m,w,p,it)-> [r,m,w,p,it]],

	new := (self, r, m, w, p, it) >> SPL(WithBases(self, rec(
		    _children  := [r, m, w, p, it],
		    dimensions := [ Length(r[1])*Length(r[1][1]), Length(r[1])*Length(r[1][1])],
		    streamSize := Length(r[1][1]),
	     ))),

	rChildren := self >> self._children,
	rSetChild := meth(self, n, what) self._children[n] := what; end,
	child     := (self, n) >> self._children[n],
	children  := self >> self._children,

	createCode := self >> self,
    print      := (self,i,is) >> Print(self.name, "(", self.dims()[1],
	    ", ", self.streamSize, ", ", self.child(1), ", ", self.child(2),
	    ", ", self.child(3), ", ", self.child(5), ")"),

    toAMat := meth(self)
        local prms, len, it, i, res;
        prms := self.child(4);
        len  := Length(prms);
        it   := self.child(5);
        res  := prms[len];

        for i in Reversed([1..len-1]) do
            res := COND(eq(it, i-1), prms[i], res);
        od;

        return res.toAMat();
    end,

	dims   := self >> [Length(self._children[1][1])*Length(self._children[1][1][1]),
	                   Length(self._children[1][1])*Length(self._children[1][1][1])],

	sums   := self >> self
));

PadToTwoPowSize := meth(muxaddr)
   local n, n2p, i, res, dif, line;
   n := Length(muxaddr[1]);

   if (2^Log2Int(n) = n) then
       return muxaddr;
   fi;

   n2p := 2^(Log2Int(n)+1);
   dif := n2p-n;

   res := [];

   for i in [1..Length(muxaddr)] do
       line := Concatenation(muxaddr[i], [n..n2p-1]);
       Append(res, [line]);
   od;

   return res;
end;

RandomVectorVector := meth(domain, inner, outer)
   local res, i, j;
   res := [];
   for i in [1..outer] do
      res[i] := [];
      for j in [1..inner] do
         res[i][j] := RandomList([0..domain-1]);
      od;
   od;
   return res;
end;

## ======================================================
##  A container for a streaming multi-permutation structure
##
##  StreamPerm([<SPL>], <m>, <w>, <i>)
##
##  where the permutations are I(<M>) x <SPL>, <SPL> is
##  the permutation as an SPL object, <w> is the
##  streaming width, and <i> selects between the <SPL>.
## ======================================================

Class(StreamPerm, BaseMat, SumsBase, rec(
	abbrevs := [(s, m, w, i) -> [s, m, w, i]],

	new := (self, prm, m, ss, i) >> SPL(WithBases(self, rec(
	     _children := [prm, m, ss, i],
	     streamSize := ss,
         par := m,
         it := i,
	     dimensions := Cond(IsBound(prm[1].domain), [m * prm[1].domain(), m * prm[1].range()], m * prm[1].dims()),
	     ))),

	rChildren := self >> self._children,
	rSetChild := meth(self, n, what) self._children[n] := what; end,
	child := (self, n) >> self._children[n],
	children := self >> self._children,

    createCode := meth(self)
        local lin, prm, w, P, length, permobj, permrows, modmatrix, muxaddr, muxaddr2, ports, addrs,
              rdaddr, wraddr, network, res, p3, p1, r, k, M, N, P, x, i, j, l, K, size, count, ptrans,
              z, G, q1, q3, q3b, p1b, G, dm, plist, bitlist, prms, Pt, thisPerm, thisPrm, prmCode, it,
              prmTens, dm_no_tens, prm_no_tens;

        prm := List(self.child(1), p->Tensor(I(self.child(2)), p));
        prm_no_tens := self.child(1);
        w := self.child(3);
        it := self.child(4);

        P := [];

        # If the permutation is a composition of multiple perms, it is more efficient to
        # compute the bit representation of each individually, and then combine them.
        for i in [1..Length(self.child(1))] do
            thisPerm := self.child(1)[i];
            thisPrm := prm[i];

	    if (ObjId(thisPrm) = L) then
		thisPrm := TL(thisPerm.params[1], thisPerm.params[2]);
	    fi;

	    if (ObjId(thisPrm) = Tensor and ObjId(thisPrm.child(1)) = I and ObjId(thisPrm.child(2)) = L) then
		thisPrm := TL(thisPrm.child(2).params[1], thisPrm.child(2).params[2], thisPrm.child(1).params[1], 1);
	    fi;

            if (ObjId(thisPerm) = Compose) then
                plist := thisPerm.children();

		# If we just have an L, turn it into a TL so we can use its .permBits() function
		plist := List(plist, i -> Cond(ObjId(i) = L, TL(i.params[1], i.params[2]), i));

		# If we have I x L, turn it into TL
		plist := List(plist, i -> Cond(ObjId(i) = Tensor and ObjId(i.child(1)) = I and ObjId(i.child(2)) = L, TL(i.child(2).params[1], i.child(2).params[2], i.child(1).params[1], 1), i));

                bitlist := List(plist, i ->
		    Cond(IsBound(i.permBits),
			i.permBits(),
			Cond(ObjId(i) = Tensor and ObjId(i.child(1)) = I and IsBound(i.child(2).permBits),
				DirectSumMat(MatSPL(I(Log2Int(i.child(1).params[1])))*GF(2).one, i.child(2).permBits()),
				PermMatrixToBits(MatSPL(i))

			)
		    )
		);

                if (-1 in bitlist) then
                    Pt := -1;
                else
                    Pt := Product(bitlist);
                    if (self.child(2) > 1) then
                        Pt := DirectSumMat(MatSPL(I(Log2Int(self.child(2))))*GF(2).one, Pt);
                    fi;
                fi;
            else
		Pt := Cond(IsBound(thisPrm.permBits),
                    # If P.permBits defined, use it
		    thisPrm.permBits(),
	            # If P = Tensor(I, Q) and Q.permBits defined, use it.
		    Cond(ObjId(thisPrm) = Tensor and ObjId(thisPrm.child(1)) = I and IsBound(thisPrm.child(2).permBits),
			DirectSumMat(MatSPL(I(Log2Int(thisPrm.child(1).params[1])))*GF(2).one, thisPrm.child(2).permBits()),

			# Otherise, use PermMatrixToBits function (slow)
			PermMatrixToBits(MatSPL(thisPrm))
		    )
		);
            fi;

            Append(P, [Pt]);
        od;

        # so, Make a list of Ps, one for each perm.  then set lin if all are <> -1.
        lin := Cond(ForAny(P, i->i=-1) or 2^Log2Int(w) <> w or Length(prm) > 1, 0, 1);

        # Find the overall dimensions and the dimension of the "non-tensor" part
        dm := Cond(IsBound(prm[1].domain), prm[1].domain(), prm[1].dims()[1]);

	# PM: This is not very robust.
        dm_no_tens := Cond(IsBound(self.child(1)[1].domain), self.child(1)[1].domain(), self.child(1)[1].dims()[1]);

        # If the permutations are on w words, then we can generate code.
        if (self.child(3) = dm_no_tens) then

            ## If this perm is multiple permutation functions composed, separate them to make the
            ## code generation work as expected.

            prmCode := List(prm_no_tens, j ->
                Cond(ObjId(j) = Compose,
                    Compose(List(j.children(), i->Prm(FPerm(i)))),
                    Prm(FPerm(j))
                    )
                );

            res := prmCode[Length(prmCode)];

            for i in Reversed([1..Length(prmCode)-1]) do
            res := COND(eq(it, (i-1)), prmCode[i], res);
            od;

            if (self.child(2) > 1) then
                return STensor(CodeBlock(res).createCode(), self.child(2), self.child(3));
            fi;

            return CodeBlock(res).createCode();

        fi;

        # If our perm is not linear on the bits, or if we have multiple permutations, or
        # if we have manually overridden this to avoid the patented permutations:
        if ((lin = 0) or (stream.avoidPermPatent = 1)) then

            # We want to ignore the tensor product until the end
            prm := self.child(1);
            prmTens := List(self.child(1), p->Tensor(I(self.child(2)), p));

            it := self.child(4);

            if (self.child(3) = 1) then
                return BRAMPermStreamOne(
#                           List(prmTens, i->PermMatrixRowValsOld(MatSPL(i))),
                           List(prmTens, i->PermMatrixRowVals(i)),
                           prmTens,
                           it);
            fi;

	        length    := self._children[1][1].dims()[1];

	        permobj   := List(self._children[1], i -> PermSPL(i));
	        permrows  := List(permobj, i -> PermMatrixRowValsOld(MatPerm(i, length)));
	        modmatrix := List(permobj, i -> PermutationModMatrix(i, length, self.child(3)));
	        muxaddr   := List(modmatrix, i -> FactorSemiMagicSquare(i));

            muxaddr2  := List(muxaddr, i -> PadToTwoPowSize(i));

            network   := List(muxaddr2, j -> List([1..Length(j)], i -> routePerm(j[i])));

	        addrs     := List([1..Length(permobj)], i -> PermHelperFindAddresses(permrows[i], muxaddr[i], self.child(3)));
	        rdaddr    := List(addrs, i -> i[1]);
	        wraddr    := List(addrs, i -> i[2]);

            if (self.child(2) > 1) then
                return STensor(BRAMPermGeneral(rdaddr, network, wraddr, self._children[1], it), self.child(2), w);
            else
                return BRAMPermGeneral(rdaddr, network, wraddr, self._children[1], it);
            fi;

        else
            # If we are here, we only have one permutation
            P := P[1];

	        k := Log2Int(self.child(3));
	        size := Log2Int(self.dimensions[1]);

            # If our streaming width is 1, then we are done.  P = I*P.
	        if (self.child(3) = 1) then
                return BRAMPerm(MatSPL(I(size))*GF(2).one, P, 1);
	        fi;

	        # if P is the bit form of I tensor P', we can remove the top-left corner bits
	        # (up to (size-k) bits, and replace them with a streaming tensor product of a
	        # smaller permutation
	        count := GetTopLeftOnes(P,k);

	        P := P{[count+1..Length(P)]}{[count+1..Length(P)]};

	        size := size-count;

	        p3 := extractP3(P, k);
	        p1 := extractP1(P, k);

	        r := Rank(p1);

	        if (r = k) then
	            M := P;
	            N := MatSPL(I(Length(M))) * GF(2).one;

	        else # This is algorithm 5.2 of J. ACM 2009 paper.

                     # Is P a permutation matrix?
                     if (CheckPerm(P) = 1) then
	                  j := NonZeroRows(p3);
	                  i := ZeroRows(p1);
	             else
		          p1b := TransposedMat(Copy(p1));
		          G := MyTriangulizeMat(p1b);
		          G := TransposedMat(G);
		          G := G * GF(2).one;
		          q1 := p1 * G;
		          q3 := p3 * G;

 		          z := LinearIndependentColumns(TransposedMat(q1));
 		          i := [];

 		          for l in [1 .. k] do
   		               if l in z then
 		                    ;
                               else
 		                    Add(i, l);
 		               fi;
 		          od;

 		          q3b := [];

 		          for l in [1 .. Length(q3)] do
 		               Append(q3b, [q3[l]{[r+1..k]}]);
 		          od;

 		          j := LinearIndependentColumns(TransposedMat(q3b));
                     fi;

	             K := buildH2(i, j, size, k);

	             N := buildH(size, k, K) * GF(2).one;
	             M := N*P;
	        fi;

	        if (Maximum(Maximum(N * M - P)) <> 0 * GF(2).one) then
	            Print("ERROR: Could not factor permutation matrix.\n");
	        fi;

	        if (count = 0) then
	            return BRAMPerm(N, M, self.child(3));
	        else
	            return STensor(BRAMPerm(N,M,self.child(3)), 2^count, self.child(3));
	        fi;
        fi;
    end,

    print := (self,i,is) >> Print(self.name, "(", self._children[1], ", ", self.child(2), ", ", self.child(3), ", ", self._children[4], ")"),

    toAMat := meth(self)
        local prms, len, it, i, res;
        prms := self._children[1];
        len  := Length(prms);
        it   := self._children[4];
        res  := Tensor(I(self.child(2)), prms[len]);

        for i in Reversed([1..len-1]) do
            res := COND(eq(it, (i-1)), Tensor(I(self.child(2)), prms[i]), res);
        od;

        return res.toAMat();
    end,

	dims := self >> self.child(2) * self._children[1][1].dims(),
	sums := self >> self

));

## ======================================================
##  A container for a streaming RC(permutation)
##
##  RCStreamPerm([<SPL>], <m>, <w>, <i>)
##
##  where the permutations is I(<M>) x [<SPL>] x I(2), [<SPL>] is
##  list of permutations as SPL objects, <w> is the
##  streaming width, and <i> selects which of the perms.
## ======================================================
Class(RCStreamPerm, BaseMat, SumsBase, rec(
	abbrevs := [(s, m, w, i) -> [s, m, w, i]],

	new := (self, prm, m, ss, i) >> SPL(WithBases(self, rec(
	     _children := [prm, m, ss, i],
	     streamSize := ss,
         par := m,
         it := i,
	     dimensions := Cond(IsBound(prm[1].domain), [2 * m * prm[1].domain(), 2 * m * prm[1].range()], 2 * m * prm[1].dims()),
	     ))),

	rChildren := self >> self._children,
	rSetChild := meth(self, n, what) self._children[n] := what; end,
	child := (self, n) >> self._children[n],
	children := self >> self._children,

    createCode := meth(self)
        local r;
        r := StreamPerm(self.child(1), self.par, self.streamSize, self.it).createCode();
        if (ObjId(r) = STensor) then
            return STensor(RC(r.child(1)), r.p, r.bs*2);
        else
             return RC(r);
        fi;
    end,

    print := (self,i,is) >> Print(self.name, "(", self._children[1], ", ", self.par, ", ", self.streamSize, ", ", self._children[4], ")"),

	toAMat := self >> RC(StreamPerm(self._children[1], self.par, self.streamSize, self.it)).toAMat(),

	dims := self >> 2 * self.par * self._children[1][1].dims(),
	sums := self >> self

));

## ======================================================
##  A container for a general streaming permutation (not
##  necessarily linear on the bits)
##
##  StreamPermGen(<SPL>, <q>)
##
##  where <SPL> is the permutation as an SPL object, and
##  <q> is the streaming width.
## ======================================================
## NB: Deprecated.  Use StreamPerm, which covers all cases
# Class(StreamPermGen, BaseMat, SumsBase, rec(
# 	abbrevs := [(s, q) -> [s, q]],

# 	new := (self, prm, ss) >> SPL(WithBases(self, rec(
# 	     _children := [prm],
# 	     streamSize := ss,

# 	     dimensions := Cond(IsBound(prm.domain), [prm.domain(), prm.range()], prm.dims()),

# 	     ))),

# 	rChildren := self >> self._children,
# 	rSetChild := meth(self, n, what) self._children[n] := what; end,
# 	child := (self, n) >> self._children[1],
# 	children := self >> self._children,

# 	createCode := meth(self)
# 	   local length, permobj, permrows, modmatrix, muxaddr, muxaddr2, ports,
#              addrs, rdaddr, wraddr, network, dms, prms;

#        dms := Cond(IsBound(self.child(1).domain), [self.child(1).domain(), self.child(1).range()], self.child(1).dims());

#        if (self.streamSize = 1) then
#            return BRAMPermStreamOne(
#                     [PermMatrixRowVals(MatSPL(self._children[1]))],
#                     [self._children[1]], 0);
#        fi;

#        if (self.streamSize = dms[1]) then

#            ## If this perm is multiple permutation functions composed, separate them to make the
#            ## code generation work as expected.
#            if (ObjId(self.child(1)) = Compose) then
#                prms := Compose(List(self.child(1).children(), i->Prm(FPerm(i))));
#            else
#                prms := Prm(FPerm(self.child(1)));
#            fi;

#            return CodeBlock(prms).createCode();

#        fi;

# 	   length    := self._children[1].dims()[1];
# 	   permobj   := PermSPL(self._children[1]);
# 	   permrows  := PermMatrixRowVals(MatPerm(permobj, length));
# 	   modmatrix := PermutationModMatrix(permobj, length, self.streamSize);
# 	   muxaddr   := FactorSemiMagicSquare(modmatrix);

#        muxaddr2  := PadToTwoPowSize(muxaddr);

# #       Print("muxaddr = ", muxaddr, "\nmuxaddr2 = ", muxaddr2);

#        network   := List([1..Length(muxaddr2)], i -> routePerm(muxaddr2[i]));

# 	   addrs     := PermHelperFindAddresses(permrows, muxaddr,
# 	                   self.streamSize);
# 	   rdaddr    := addrs[1];
# 	   wraddr    := addrs[2];

# #!	   rdaddr := RandomVectorVector(length/self.streamSize, self.streamSize, length/self.streamSize);
# #!	   wraddr := RandomVectorVector(length/self.streamSize, self.streamSize, length/self.streamSize);
# #!	   muxaddr := RandomVectorVector(self.streamSize, self.streamSize, length/self.streamSize);

# # Previously, we used 'muxaddr.'
# #	   return BRAMPermGeneral(rdaddr, muxaddr, wraddr, self._children[1]);
# # Now I am switching to the omega^-1 omega network control bits
#        return BRAMPermGeneral(rdaddr, network, wraddr, self._children[1]);

#         end,

# 	print := (self,i,is) >> Print(self.name, "(", self._children[1], ", ",
# 	                               self.streamSize, ")"),

# 	toAMat := self >> self._children[1].toAMat(),
# 	dims := self >> self._children[1].dims(),
# 	sums := self >> self

# ));

## ======================================================
##  A container for a streaming multi-permutation structure
##
##  StreamPermGen(<l>, <i>, <q>)
##
##  where <l> is a list of permutations as SPL objects,
##  <i> is a variable that selects which permutation to
##  perform, and <q> is the streaming width.
## ======================================================
## Deprecated.  Use StreamPerm, which supports multiple perms (or will soon!)

##  !!!!!!!!!!! IMPORTANT !!!!!!!!
#     Do not delete this code until I have successfully rolled its multiple perm
#     support into StreamPermGen.
##  !!!!!!!!!!! IMPORTANT !!!!!!!!

# Class(StreamPermGenMult, BaseMat, SumsBase, rec(
# 	abbrevs := [(s, i, q) -> [s, i, q]],

# 	new := (self, prm, i, ss) >> SPL(WithBases(self, rec(
# 	     _children := [prm, i],
# 	     streamSize := ss,

# 	     dimensions := Cond(IsBound(prm[1].domain), [prm[1].domain(), prm[1].range()], prm[1].dims()),

# 	     ))),

# 	rChildren := self >> self._children,
# 	rSetChild := meth(self, n, what) self._children[n] := what; end,
# 	child := (self, n) >> self._children[n],
# 	children := self >> self._children,

# 	createCode := meth(self)
# 	   local length, permobj, permrows, modmatrix, muxaddr, muxaddr2, ports,
#              addrs, rdaddr, wraddr, network, dms, prms, it, prmcode, i, res;

#        dms := Cond(IsBound(self.child(1)[1].domain), [self.child(1)[1].domain(), self.child(1)[1].range()], self.child(1)[1].dims());

#        it   := self.child(2);
#        prms := self.child(1);

#        if (self.streamSize = 1) then
#            return BRAMPermStreamOne(List(prms, i->PermMatrixRowVals(MatSPL(i))),
#                       prms,
#                       it);
#        fi;

#        if (self.streamSize = dms[1]) then

#            ## If this perm is multiple permutation functions composed, separate them to make the
#            ## code generation work as expected.

#            prmcode := List(self.child(1), j ->
#                         Cond(ObjId(j)=Compose,
#                            Compose(List(self.child(1).children(), i->Prm(FPerm(i)))),
#                            Prm(FPerm(j))
#                         )
#            );

#            res := prmcode[Length(prmcode)];

#            for i in Reversed([1..Length(prmcode)-1]) do
#               res := COND(eq(it, i), prmcode[i], res);
#            od;

#            return CodeBlock(res).createCode();

#        fi;

# 	   length    := self._children[1].dims()[1];
# 	   permobj   := PermSPL(self._children[1]);
# 	   permrows  := PermMatrixRowVals(MatPerm(permobj, length));
# 	   modmatrix := PermutationModMatrix(permobj, length, self.streamSize);
# 	   muxaddr   := FactorSemiMagicSquare(modmatrix);

#        muxaddr2  := PadToTwoPowSize(muxaddr);

#        network   := List([1..Length(muxaddr2)], i -> routePerm(muxaddr2[i]));

# 	   addrs     := PermHelperFindAddresses(permrows, muxaddr,
# 	                   self.streamSize);
# 	   rdaddr    := addrs[1];
# 	   wraddr    := addrs[2];

# # Previously, we used 'muxaddr.'
# #	   return BRAMPermGeneral(rdaddr, muxaddr, wraddr, self._children[1]);
# # Now I am switching to the omega^-1 omega network control bits
#        return BRAMPermGeneral(rdaddr, network, wraddr, self._children[1]);

#         end,

# 	print := (self,i,is) >> Print(self.name, "(", self._children[1], ", ", self._children[2], ", ",
# 	                               self.streamSize, ")"),

#     toAMat := meth(self)
#         local prms, len, it, i, res;

#          prms := self._children[1];
#          len  := Length(prms);
#          it   := self._children[2];
#          res  := prms[len];

#          for i in Reversed([1..len-1]) do
#              res := COND(eq(it, i), prms[i], res);
#          od;

#          return res.toAMat();
#     end,

# #    toAMat := self >> COND(eq(self._children[2], 0), self._children[1][1], self._children[1][2]).toAMat(),
# 	dims := self >> self._children[1][1].dims(),
# 	sums := self >> self

# ));

## ==================================================================
##   BRAMPermStreamOne([rd-addr, ...], [<SPL>, ... ], i)
##     A container for a streaming multi-permutation structure where width = 1.
##     This merits its own class because the hardware implementation
##     is greatly simplified.
##
##     The permutations are given as a list of vectors of read addresses, and
##     as a list of SPL objects.
##
##     i is the variable that selects which permutation to perform
## ==================================================================
Class(BRAMPermStreamOne, BaseMat, SumsBase, rec(
	abbrevs := [(r, p, i)-> [r, p, i]],

	new := (self, r, p, i) >> SPL(WithBases(self, rec(
		    _children  := [r, p, i],
		    dimensions := [ Length(r[1]), Length(r[1])]
	     ))),

	rChildren := self >> self._children,
	rSetChild := meth(self, n, what) self._children[n] := what; end,
	child     := (self, n) >> self._children[n],
	children  := self >> self._children,

	createCode := self >> self,

    print      := (self,i,is) >> Print(self.name, "(", self.child(1), ", ", self.child(3), ")"),

#	toAMat := self >> self.child(2).toAMat(),

    toAMat := meth(self)
        local prms, len, it, i, res;

         prms := self._children[2];
         len  := Length(prms);
         it   := self._children[3];
         res  := prms[len];

         for i in Reversed([1..len-1]) do
             res := COND(eq(it, i-1), prms[i], res);
         od;

         return res.toAMat();
    end,

	dims   := self >> [Length(self._children[1][1]),
	                   Length(self._children[1][1])],

	sums   := self >> self
));

Declare(InitStreamUnrollHw, SumStreamStrategy, StreamStrategy);

# CountPermSwitches(t)
# Returns number of switches needed for permutation t.
# We assume t is a TPrm().withTags([AStream(w)]);
CountPermSwitches := function (t)
  local i, M, M2, M2_t, N, N2, N2_t, n, k, r, s, s2, s3, s_m, s_n, opts;

  opts := InitStreamUnrollHw();
  r := RandomRuleTree(t, opts);
  s := SumsRuleTreeStrategy(r, SumStreamStrategy, opts);
  s2 := ApplyStrategy(s, StreamStrategy, UntilDone, opts);
  s3 := s2.createCode();

  k := Log2Int(s3.streamSize);
  M := s3.child(2);
  n := Length(M);

  # Extract M2.  First just get the bottom k rows.
  M2_t := Sublist(M, [n-k+1 .. n]);

  M2 := [];
  # Now get the first n-k columns
  for i in [1..k] do
     Append(M2, [Sublist(M2_t[i], [1..k])]);
  od;

  s_m := Rank(M2);

  N := s3.child(1);
  # Extract N2.  First just get the bottom k rows.
  N2_t := Sublist(N, [n-k+1 .. n]);

  N2 := [];
  # Now get the first n-k columns
  for i in [1..k] do
     Append(N2, [Sublist(N2_t[i], [1..k])]);
  od;

  s_n := Rank(N2);

  return (s_m + s_n) * s3.streamSize/2;
end;


# This file is a script for GAP and tests a list of polynomials in hexadecimal
# for irreducibility over F_2

# create x over F_2 = GF(2)
x := Indeterminate(GF(2), "x");

# test if polynomial is irreducible, i.e. the number of factors is one
IrredPoly := function (poly)
    return (Length(Factors(poly)) = 1);
end;;

# create a polynomial in x from the hexadecimal representation of the
# coefficients
Hex2Poly := function (s)
    return ValuePol(CoefficientsQadic(IntHexString(s), 2), x);
end;;

# list of candidates, in hex
candidates := [ "3DA3358B4DC173" ];

# create real polynomials
L := List(candidates, Hex2Poly);

# filter and display the list of irreducible polynomials contained in L
Display(Filtered(L, x -> (IrredPoly(x))));


# Copyright (c) 2018-2020, Carnegie Mellon University
# See LICENSE for details
#
# n-qubit T and Tdag transforms
#

#F qTT( n, i ) - T Gate non-terminal
#F Definition: (2^n x 2^n)-matrix  that applies an n-point T Transform to the target qubits
#F Note:       qTT(1, 1) denotes the matrix[[1, 0], [0, e^(-ipi/4)]], and qTT(1, -1) denotes the matrix[[1, 0], [0, e^(ipi/4)]]
#F             qTT(_) is symmetric.
#F
#F qTT(n , i) -> an n-qubit T transform, or Tdag transform if i = -1
Class(qTT, TaggedNonTerminal, rec(
  abbrevs   := [ (n, i) -> Checked(IsPosInt(n), [n, When(i <= -1, -1, 1)]) ],
  dims      := self >> let(size := 2^self.params[1], [size, size]),
  terminate := self >> When(self.params[2] = -1, Tensor(Replicate(self.params[1], qTdag())), Tensor(Replicate(self.params[1], qT()))),
  isReal    := self >> false,
  rChildren := self >> self.params,
  from_rChildren := (self, rch) >> self.__bases__[1](rch[1], rch[2]),
  groups    := self >> [Replicate(self.params[1], 1)],
  recursive_def := (self, arch) >> self.__bases__[1](self.params[1], self.params[2]),
  SmallRandom := () -> Random([2..5]),
  LargeRandom := () -> Random([6..15]),
  normalizedArithCost := self >> Error("ArithCost not implemented"),
  TType := T_Complex(64),
));

NewRulesFor(qTT, rec(

    # qTT_BinSplit rule
    # qTT_BinSplit qTT_(k) -> (qTT_(k1) tensor I) (I tensor qTT_(k2))
    # k1 + k2 = k
    qTT_BinSplit := rec (
        forTransposition := false,
        minSize          := 2,
        applicable       := (self, nt) >> nt.params[1] > 1,
        children         := nt -> List( [1..nt.params[1] - 1],  i -> [ Tensor(qTT(i, nt.params[2]), qTT(nt.params[1]-i, nt.params[2])).withTags(nt.getTags()) ]  ),
        apply            := (nt, c, cnt) -> c[1],
        switch := true,
    ),

    #F qTT_Base: qTT(1, _) = qT() or qT().inverse() (i.e. qTdag) SPL object
    #F Directly represent as an implementable gate
    qTT_Base := rec(
        info             := "qTT_(1, _) -> qT()",
        forTransposition := false,
        applicable       := (self, nt) >> nt.params[1]=1,
        apply            := (nt, c, cnt) -> When(nt.params[2] = -1, qTdag(), qT()),
    )

));


fib := function(n)
  local a;
  a := [[0, 1], [1, 1]]^n;
  return a[1][2];
end;


# First position is built-in
haystack := Eratosthenes(10000);;
needle := 8999;;
Position(haystack, needle);
# 1117

LastPosition := function(L, x)
  local old, new;
  old := 0;
  new := 0;
  while new <> fail do
    new := Position(L, x, old);
    if new <> fail then
      old := new;
    fi;
  od;
  return old;
end;

a := Shuffle(List([1 .. 100], x -> x mod 10));
# [ 0, 2, 4, 5, 3, 1, 0, 4, 8, 8, 2, 7, 6, 3, 3, 6, 4, 4, 3, 0, 7, 1, 8, 7, 2, 4, 7, 9, 4, 9, 4, 5, 9, 9, 6, 7, 8, 2, 3,
#   5, 1, 5, 4, 2, 0, 9, 6, 1, 1, 2, 2, 0, 5, 7, 6, 8, 8, 3, 1, 9, 5, 1, 9, 6, 8, 9, 2, 0, 6, 2, 1, 6, 1, 1, 2, 5, 3, 3,
#   0, 3, 5, 7, 5, 4, 6, 8, 0, 9, 8, 3, 7, 8, 0, 4, 9, 7, 0, 6, 5, 7 ]
Position(a, 0);
# 1
LastPosition(a, 0);
# 97


RequirePackage("grape");

# bilinear forms graph: vozlišča so matrike,
# dve vozlišči sta sosednji, ko je njuna razlika ranga 1
GB := AdjFunGraph(Elements(GF(2)^[2, 3]),
    function(x,y) return RankMat(x-y) = 1; end);;

# OA(8, 3): vozlišča so vnosi v latinskem kvadratu (Z_8)^2,
# dve vozlišči sta sosednji, če sta v isti vrstici, v istem stolpcu,
# ali pa imata isto vrednost
GZ8 := AdjFunGraph(Elements(ZmodnZ(8)^2), function(x,y) return x <> y and
        (x[1] = y[1] or x[2] = y[2] or x[1]+x[2] = y[1]+y[2]); end);;

# OA(8, 3) za latinski kvadrat (Z_2 × Z_4)^2
A := Elements(AbelianGroup([2, 4]));;
A2 := Cartesian(A, A);;
GZ24 := AdjFunGraph(A2, function(x,y) return x <> y and
        (x[1] = y[1] or x[2] = y[2] or x[1]*x[2] = y[1]*y[2]); end);;

# OA(8, 3) za latinski kvadrat (Dih(8))^2
D := Elements(DihedralGroup(8));;
D2 := Cartesian(D, D);;
GD8 := AdjFunGraph(D2, function(x,y) return x <> y and
        (x[1] = y[1] or x[2] = y[2] or x[1]*x[2] = y[1]*y[2]); end);;

# OA(8, 3) za latinski kvadrat (Q_8)^2
Q := Elements(SmallGroup(8, 4));;
Q2 := Cartesian(Q, Q);;
GQ8 := AdjFunGraph(Q2, function(x,y) return x <> y and
        (x[1] = y[1] or x[2] = y[2] or x[1]*x[2] = y[1]*y[2]); end);;

# iz [21, 6; 8:21, 12:42]_2 kode
M := [
    [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
    [1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0],
    [1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0],
    [1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0],
    [1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0],
    [1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1]
]*Z(2);

# vozlišča so kodne besede,
# dve vozlišči sta sosednji, ko je razdalja med njima minimalna
GC := AdjFunGraph(Elements(Subspace(GF(2)^21, M)),
    function(x, y) return WeightVecFFE(x+y) = 8; end);;
lGC := DistanceSetInduced(GC, 1, 1);;


Composition := function(f, g)
    return x -> f(g(x));
end;

h := Composition(x -> x+1, x -> x*x);
h(5);
# 26


# Copyright (c) 2018-2020, Carnegie Mellon University
# See LICENSE for details
#
# n-qubit S transform
#

#F qST( n, i ) - S Gate non-terminal
#F Definition: (2^n x 2^n)-matrix  that applies an n-point S Transform to the target qubits
#F Note:       qST(1) denotes the matrix[[1, 0], [0, e^(-ipi/2)]], or [[1,0], [0, -i]]
#F             qZT(_) is symmetric.
#F
#F qST(n , i) -> an n-qubit S transform, or Sdag transform if i = -1
Class(qST, TaggedNonTerminal, rec(
  abbrevs   := [ (n, i) -> Checked(IsPosInt(n), [n, When(i <= -1, -1, 1)]) ],
  dims      := self >> let(size := 2^self.params[1], [size, size]),
  terminate := self >> When(self.params[2] = -1, Tensor(Replicate(self.params[1], qSdag())), Tensor(Replicate(self.params[1], qS()))),
  isReal    := self >> false,
  rChildren := self >> self.params,
  from_rChildren := (self, rch) >> self.__bases__[1](rch[1], rch[2]),
  groups    := self >> [Replicate(self.params[1], 1)],
  recursive_def := (self, arch) >> self.__bases__[1](self.params[1], self.params[2]),
  SmallRandom := () -> Random([2..5]),
  LargeRandom := () -> Random([6..15]),
  normalizedArithCost := self >> Error("ArithCost not implemented"),
  TType := T_Complex(64),
));

NewRulesFor(qST, rec(

    # qST_BinSplit rule
    # qST_BinSplit qST_(k) -> (qST_(k1) tensor I) (I tensor qST_(k2))
    # k1 + k2 = k
    qST_BinSplit := rec (
        forTransposition := false,
        minSize          := 2,
        applicable       := (self, nt) >> nt.params[1] > 1,
        children         := nt -> List( [1..nt.params[1] - 1],  i -> [ Tensor(qST(i, nt.params[2]), qST(nt.params[1]-i, nt.params[2])).withTags(nt.getTags()) ]  ),
        apply            := (nt, c, cnt) -> c[1],
        switch := true,
    ),

    #F qST_Base: qST(1, _) = qS() or qS().inverse() SPL object
    #F Directly represent as an implementable gate
    qST_Base := rec(
        info             := "qST_(1, _) -> qS()",
        forTransposition := false,
        applicable       := (self, nt) >> nt.params[1]=1,
        apply            := (nt, c, cnt) -> When(nt.params[2] = -1, qSdag(), qS()),
    )

));


# Solution in '''RPN'''
Play24 := function()
	local input, digits, line, c, chars, stack, stackptr, cur, p, q, ok, a, b, run;
	input := InputTextUser();
	run := true;
	while run do
		digits := List([1 .. 4], n -> Random(1, 9));
		while true do
			Display(digits);
			line := ReadLine(input);
			line := Chomp(line);
			if line = "end" then
				run := false;
				break;
			elif line = "next" then
				break;
			else
				ok := true;
				stack := [ ];
				stackptr := 0;
				chars := "123456789+-*/ ";
				cur := ShallowCopy(digits);
				for c in line do
					if c = ' ' then
						continue;
					fi;
					p := Position(chars, c);
					if p = fail then
						ok := false;
						break;
					fi;
					if p < 10 then
						q := Position(cur, p);
						if q = fail then
							ok := false;
							break;
						fi;
						Unbind(cur[q]);
						stackptr := stackptr + 1;
						stack[stackptr] := p;
					else
						if stackptr < 2 then
							ok := false;
							break;
						fi;
						b := stack[stackptr];
						a := stack[stackptr - 1];
						stackptr := stackptr - 1;
						if c = '+' then
							a := a + b;
						elif c = '-' then
							a := a - b;
						elif c = '*' then
							a := a * b;
						elif c = '/' then
							if b = 0 then
								ok := false;
								break;
							fi;
							a := a / b;
						else
							ok := false;
							break;
						fi;
						stack[stackptr] := a;
					fi;
				od;
				if ok and stackptr = 1 and Size(cur) = 0 then
					if stack[1] = 24 then
						Print("Good !\n");
						break;
					else
						Print("Bad value: ", stack[1], "\n");
						continue;
					fi;
				fi;
				Print("Invalid expression\n");
			fi;
		od;
	od;
	CloseStream(input);
end;

# example session
# type "end" to quit the game, "next" to try another list of digits
gap> Play24();
[ 7, 6, 8, 5 ]
86*75-/
Good !
[ 5, 9, 2, 7 ]
end
gap>


# Copyright (c) 2018-2020, Carnegie Mellon University
# See LICENSE for details
#
# n-qubit X transforms
#

#F qXT( n ) - X Gate non-terminal
#F Definition: (2^n x 2^n)-matrix  that applies an n-point X Transform to the target qubits
#F Note:       qXT(1) denotes the matrix[[0, 1], [1, 0]],
#F             qXT(_) is symmetric.
#F
#F qXT( n ) -> an n-qubit X transform
Class(qXT, TaggedNonTerminal, rec(
  abbrevs   := [ n -> Checked(IsPosInt(n), [n]) ],
  dims      := self >> let(size := 2^self.params[1], [size, size]),
  terminate := self >> Tensor(Replicate(self.params[1], qX())),
  isReal    := self >> true,
  groups    := self >> [Replicate(self.params[1], 1)],
  rChildren := self >> self.params,
  from_rChildren := (self, rch) >> self.__bases__[1](rch[1]),
  recursive_def := (self, arch) >> self.__bases__[1](self.params[1]),
  connected := self >> true,
  SmallRandom := () -> Random([2..5]),
  LargeRandom := () -> Random([6..15]),
  normalizedArithCost := self >> Error("ArithCost not implemented"),
  TType := T_Complex(64),
));

NewRulesFor(qXT, rec(

    # qXT_BinSplit rule
    # qXT_BinSplit qXT_(k) -> (qXT_(k1) tensor I) (I tensor qXT_(k2))
    # k1 + k2 = k
    qXT_BinSplit := rec (
        forTransposition := false,
        minSize          := 2,
        applicable       := (self, nt) >> nt.params[1] > 1,
        children         := nt -> List( [1..nt.params[1] - 1],  i -> [ Tensor(qXT(i), qXT(nt.params[1]-i)).withTags(nt.getTags()) ]  ),
        apply            := (nt, c, cnt) -> c[1],
        switch := true,
    ),

    #F qXT_Base: qXT(1) = qX() SPL object
    #F Directly represent as an implementable gate
    qXT_Base := rec(
        info             := "qXT_(1) -> qX()",
        forTransposition := false,
        applicable       := (self, nt) >> nt.params[1]=1,
        apply            := (nt, c, cnt) -> qX(),
    )

));


# Copyright (c) 2018-2021, Carnegie Mellon University
# See LICENSE for details

#F SPLAMat( <amat> )
#F   returns an spl corresponding to <amat>.
#F

SPLAMat := function ( A )
  if not IsAMat(A) then
    Error("<A> must be an amat");
  elif A.char <> 0 then
    Error("<A> has char <> 0");
  fi;

  # catch identity case
  if IsIdentityMat(A) then
    return Checked(A.dimensions[1]=A.dimensions[2], I(A.dimensions[1]));
  fi;

  # dispatch on types
  if A.type = "perm" then

    return Perm(A.element, A.dimensions[1]);

  elif A.type = "mon" then

    # catch diagonal case
    if A.element.perm = () then
      return Diag(A.element.diag);
    fi;
    return Perm(A.element.perm, A.dimensions[1]) * Diag(A.element.diag);

  elif A.type = "mat" then

    # catch DFT case
    if IsBound(A.isDFT) and A.isDFT = true then
      return spiral.transforms.DFT(A.dimensions[1]);
    fi;

    # catch RDFT case
    if  IsBound(A.isRDFT) and A.isRDFT = true then
      if A.RDFTinfo[1] = 1 then
        if A.RDFTinfo[2] = "not transposed" then
          return spiral.transforms.SRDFT(A.dimensions[1]);
        else # transposed
          return spiral.transforms.SRDFT(A.dimensions[1]).transpose();
        fi;
      else # A.RDFTinfo[1] = 3
        if A.RDFTinfo[2] = "not transposed" then
          return spiral.transforms.SRDFT3(A.dimensions[1]);
        else # transposed
          return spiral.transforms.SRDFT3(A.dimensions[1]).transpose();
        fi;
      fi;
    fi;

    # catch rotation case
    if IsBound(A.isRotation) and A.isRotation = true then
      return Rot(A.angle);
    fi;

    return Mat(A.element);

  elif A.type = "scalarMultiple" then

    if IsIdentityMat(A.element) then
      return
        Diag(
          List([1..A.dimensions[1]], i -> A.scalar)
        );
    else
      return
        Compose(
          Diag(
            List([1..A.dimensions[2]], i -> A.scalar)
          ),
          SPLAMat(A.element)
        );
    fi;

  elif A.type = "product" then

    return Compose(List(A.factors, SPLAMat));

  elif A.type = "power" then

    # no negative exponents
    if A.exponent < 0 then
      Error("<A>.exponent must not be negative");
    fi;
    return Compose(Replicate(A.exponent, SPLAMat(A.element)));

  elif A.type = "conjugate" then

    return Conjugate(SPLAMat(A.element), SPLAMat(A.conjugation));

  elif A.type = "directSum" then

    return DirectSum(List(A.summands, SPLAMat));

  elif A.type = "tensorProduct" then

    return Tensor(List(A.factors, SPLAMat));

  else

    Error("unrecognized type of amat <A>");

  fi;
end;


leftOf  :=function(setA, vA, setB, vB)
local i;
for i in [1..4] do
   if ( setA[i] = vA) and  (setB[i+1] = vB) then return true ;fi;
od;
 return false;
end;

nextTo  :=function(setA, vA, setB, vB)
local i;
for i in [1..4] do
   if ( setA[i] = vA) and  (setB[i+1] = vB) then return true ;fi;
   if ( setB[i] = vB) and  (setA[i+1] = vA) then return true ;fi;
od;
return false;
end;

requires := function(setA, vA, setB, vB)
    local i;
      for i in [1..5] do
        if ( setA[i] = vA) and  (setB[i] = vB) then return true ;fi;
      od;
 return false;
end;

pcolors :=PermutationsList(["white" ,"yellow" ,"blue" ,"red" ,"green"]);
pcigars :=PermutationsList(["blends", "pall_mall", "prince", "bluemasters", "dunhill"]);
pnats:=PermutationsList(["german", "swedish", "british", "norwegian", "danish"]);
pdrinks :=PermutationsList(["beer", "water", "tea", "milk", "coffee"]);
ppets  :=PermutationsList(["birds", "cats", "horses", "fish", "dogs"]);

for colors in pcolors do
if not (leftOf(colors,"green",colors,"white")) then continue ;fi;
for nats in pnats do
if not (requires(nats,"british",colors,"red")) then  continue ;fi;
if not (nats[1]="norwegian") then continue ;fi;
if not (nextTo(nats,"norwegian",colors,"blue")) then continue ;fi;
for pets in ppets do
if not (requires(nats,"swedish",pets,"dogs")) then  continue ;fi;
for drinks in pdrinks do
if not (drinks[3]="milk") then continue ;fi;
if not (requires(colors,"green",drinks,"coffee")) then continue ;fi;
if not (requires(nats,"danish",drinks,"tea")) then  continue ;fi;
for cigars in pcigars do
if not (nextTo(pets,"horses",cigars,"dunhill")) then continue ;fi;
if not (requires(cigars,"pall_mall",pets,"birds")) then  continue ;fi;
if not (nextTo(cigars,"blends",drinks,"water")) then  continue ;fi;
if not (nextTo(cigars,"blends",pets,"cats")) then  continue ;fi;
if not (requires(nats,"german",cigars,"prince")) then  continue ;fi;
if not (requires(colors,"yellow",cigars,"dunhill")) then continue ;fi;
if not (requires(cigars,"bluemasters",drinks,"beer")) then  continue ;fi;
Print(colors,"\n");
Print(nats,"\n");
Print(drinks,"\n");
Print(pets,"\n");
Print(cigars,"\n");
od;od;od;od;od;


IsDeranged := function(a, b)
	local i, n;
	for i in [1 .. Size(a)] do
		if a[i] = b[i] then
			return false;
		fi;
	od;
	return true;
end;

# This solution will find all deranged pairs of any length.
Deranged := function(name)
	local sol, ana, u, v;
	sol := [ ];
	ana := Anagrams(name);
	for u in ana do
		for v in Combinations(u, 2) do
			if IsDeranged(v[1], v[2]) then
				Add(sol, v);
			fi;
		od;
	od;
	return sol;
end;

# Now we find all deranged pairs of maximum length
a := Deranged("unixdict.txt");;
n := Maximum(List(a, x -> Size(x[1])));
Filtered(a, x -> Size(x[1]) = n);
# [ [ "excitation", "intoxicate" ] ]


#
# Read("~/Workspace/groupsSB/epi/A2/case.2.gi");
#

type:="A";
rank:=2;
nr_pos_roots:=3;

Read("~/Workspace/groupsSB/epi/group.gi");

highest_weight:=[1,1];# corresponding highest weight module is the Lie algebra

Read("~/Workspace/groupsSB/epi/ext.gi");

#
# p>2
#

getRep:=function(k,t)
    local u,ua,ub,uab;
    ua:=ext_root_group(k,1,t);
    #ub:=ext_root_group(k,2,t);
    uab:=ext_root_group(k,3,t);
    u:=ua*uab;

    return u;
end;

#
# k:=3; # power of exterior algebra
#
handle:=function(k)
    local id_mat,u,e,h,h1,h2,ese,evh1,esh1,evh2,esh2,esh01,index_ev01,esh02,index_ev02,si,si1,si2,inter;
    u:=getRep(k,1);
    id_mat:=u^0;
    e:=u-id_mat;
    ese:=Eigenspaces(Rationals,e)[1]; #eigenvalues 0, centralizer

    # torus
    # centralizer of T1
    h1:=ext_e(k,cb0[7]);
    esh1:=Eigenspaces(Rationals,h1);
    evh1:=Eigenvalues(Rationals,h1);
    esh01:=[];
    index_ev01:=Position(evh1,0);
    esh01:=esh1[index_ev01];

    # centralizer of T2
    h2:=ext_e(k,cb0[8]);
    esh2:=Eigenspaces(Rationals,h2);
    evh2:=Eigenvalues(Rationals,h2);
    esh02:=[];
    index_ev02:=Position(evh2,0);
    esh02:=esh2[index_ev02];

    #
    # sum and intersection
    #
    #return [ese,esh01,esh02];
    si1:=SumIntersectionMat(Basis(ese),Basis(esh01));
    si2:=SumIntersectionMat(Basis(ese),Basis(esh02));
    si:=SumIntersectionMat(si1[2],si2[2]);
    inter:=si[2];
    return inter;
    #return [inter,esh01,esh02];
end;

testFixedPointsTrous:=function(k)
    local result1,result2,h1,esh1,evh1,esh01,index_ev01,h2,esh2,evh2,esh02,index_ev02;
    # torus
    # centralizer of T1
    h1:=ext_e(k,cb0[7]);
    esh1:=Eigenspaces(Rationals,h1);
    evh1:=Eigenvalues(Rationals,h1);
    esh01:=[];
    index_ev01:=Position(evh1,0);
    esh01:=esh1[index_ev01];

    result1:=List(Basis(esh01),b->ext_lin_comb(k,b));
    result1:=List(result1,i->cb0[7]^i);

    # centralizer of T2
    h2:=ext_e(k,cb0[8]);
    esh2:=Eigenspaces(Rationals,h2);
    evh2:=Eigenvalues(Rationals,h2);
    esh02:=[];
    index_ev02:=Position(evh2,0);
    esh02:=esh2[index_ev02];

    result2:=List(Basis(esh02),b->ext_lin_comb(k,b));
    result2:=List(result2,i->cb0[8]^i);

    return [result1,result2];
end;


# Checks whether a graph is an antipodal cover.
BindGlobal("IsAntipodalCover", function(G)
    local d, k, ia, i, ci, cj;
    if not IsSimpleGraph(G) then
        Error("not a simple graph");
        return fail;
    fi;
    if not IsConnectedGraph(G) then
        Error("not a connected graph");
        return fail;
    fi;
    d := Diameter(G);
    ia := GlobalParameters(G);
    k := ia[1][3];
    if k = -1 or ia[d+1][1] <> k then
        return false;
    fi;
    for i in [1..Length(G.representatives)] do
        ci := DistanceSet(G, 1, DistanceSet(G, [0, d], G.representatives[i]));
        cj := Union(List(Adjacency(G, G.representatives[i]),
                x -> DistanceSet(G, [0, d], x)));
        if ci <> cj then
            return false;
        fi;
    od;
    return true;
end);

# Covering index of an antipodal cover.
BindGlobal("AntipodalCoveringIndex", function(G)
    if not IsAntipodalCover(G) then
        Error("not an antipodal cover");
        return fail;
    fi;
    return Length(DistanceSet(G, [0, Diameter(G)], 1));
end);

# Parameters of a generalized polygon.
# Does not check whether G actually is a generalized polygon
# (for d = 2 this is not guaranteed).
BindGlobal("GeneralizedPolygonParameters", function(G)
    local d, s, t, ia;
    if not IsSimpleGraph(G) then
        Error("not a simple graph");
        return fail;
    fi;
    if not IsConnectedGraph(G) then
        Error("not a connected graph");
        return fail;
    fi;
    d := Diameter(G);
    ia := GlobalParameters(G);
    t := ia[d+1][1] - 1;
    s := ia[1][3] / ia[d+1][1];
    if not IsInt(s) or ForAny(ia{[2..d]}, x -> x[1] <> 1 or x[3] <> s*t) then
        return fail;
    fi;
    return [2*d, s, t];
end);

# Check whether two vertices of a generalized quadrangle with parameter t
# are either equal, or make a regular pair.
BindGlobal("IsRegularPair", function(G, x, y, t)
    return x = y or y in Adjacency(G, x) or
        Size(Intersection(List(Intersection(Adjacency(G, x), Adjacency(G, y)),
            z -> Adjacency(G, z)))) = t+1;
end);

# Find regular points in a generalized quadrangle.
BindGlobal("RegularPoints", function(G)
    local l, t, P, orb, par;
    par := GeneralizedPolygonParameters(G);
    if par = fail then
        return fail;
    fi;
    t := par[3];
    l := [1..Length(G.representatives)];
    P := [1..G.order];
    orb := List(l, i -> OrbitsDomain(Stabilizer(G.group,
                                                    G.representatives[i]), P));
    return G.representatives{Filtered(l, i -> ForAll(orb[i][1],
                        x -> IsRegularPair(G, G.representatives[i], x, t)))};
end);


# Copyright (c) 2018-2021, Carnegie Mellon University
# See LICENSE for details

Declare(DST3, DST7);

#F DST1(<n>) - Discrete Sine Transform, Type I, non-terminal
#F Definition: (n x n)-matrix [ sin((k+1)*(l+1)*pi/(n+1)) | k,l = 0...n-1 ]
#F Note:       The natural size for a DST1 is 2^k - 1
#F Note:       DST4 is symmetric
#F Example:    DST1(7)
#F
Class(DST1, DTTBase, rec(
    terminate := self >> When(self.params[1] > 1, Mat(DST_Iunscaled(self.params[1])), I(1)),
    transpose := self >> Copy(self),
    SmallRandom := () -> Random([2,3,5,7,11,15,23,31])
));

#F DST2(<n>) - Discrete Sine Transform, Type II, non-terminal
#F Definition: (n x n)-matrix [ sin(k*(l+1/2)*pi/n) | k,l = 0...n-1 ]
#F Note:       DST2 is the transpose of DST3
#F Example:    DST2(8)
Class(DST2, DTTBase, rec(
    terminate := self >> Mat(DST_IIunscaled(self.params[1])),
    transpose := self >> DST3(self.params[1]),
    SmallRandom := () ->  Random([2,3,4,6,8,9,12,16,18,24,27,32])
));

#F DST3(<n>) - Discrete Sine Transform, Type III, non-terminal
#F Definition: (n x n)-matrix [ sin((k-1/2)*l*pi/n) | k,l = 1...n ]
#F Note:       DST3 is the transpose of DST2
#F Example:    DST3(8)
#F Scaled variant (not supported) is:
#F                [ a_l*sin(k*(l-1/2)*pi/n) | k,l = 1...n ]
#F            with  a_j = 1/sqrt(2) for j = n and = 1 else.
Class(DST3, DTTBase, rec(
    terminate := self >> Mat(DST_IIIunscaled(self.params[1])),
    transpose := self >> DST2(self.params[1]),
    SmallRandom := () ->  Random([2,3,4,6,8,9,12,16,18,24,27,32])
));

#F DST4(<n>) - Discrete Sine Transform, Type IV, non-terminal
#F Definition: (n x n)-matrix [ sin((k-1/2)*(l-1/2)*pi/n) | k,l = 1...n ]
#F Note:       DST4 is symmetric
#F Example:    DST4(8)
Class(DST4, DTTBase, rec(
    terminate := self >> Mat(DST_IVunscaled(self.params[1])),
    transpose := self >> Copy(self),
    SmallRandom := () ->  Random([2,3,4,6,8,9,12,16,18,24,27,32])
));

#F DST5(<n>) - Discrete Sine Transform, Type V, non-terminal
#F Definition: (n x n)-matrix [ sin((k+1)*(l+1)*pi/(n+1/2)) | k,l = 0...n-1 ]
#F Note:       DST5 is symmetric
#F Example:    DST5(8)
Class(DST5, DTTBase, rec(
    terminate := self >> Mat(DST_Vunscaled(self.params[1])),
    transpose := self >> Copy(self),
));

#F DST6(<n>) - Discrete Sine Transform, Type VI, non-terminal
#F Definition: (n x n)-matrix [ sin((k+1)*(l+1/2)*pi/(n+1/2)) | k,l = 0...n-1 ]
#F Note:       The transpose of DST6 is DST7
#F Example:    DST6(8)
Class(DST6, DTTBase, rec(
    terminate := self >> Mat(DST_VIunscaled(self.params[1])),
    transpose := self >> DST7(self.params[1])
));

#F DST7(<n>) - Discrete Sine Transform, Type VII, non-terminal
#F Definition: (n x n)-matrix [ sin((k+1/2)*(l+1)*pi/(n+1/2)) | k,l = 0...n-1 ]
#F Note:       The transpose of DST7 is DST6
#F Example:    DST7(8)
Class(DST7, DTTBase, rec(
    terminate := self >> Mat(DST_VIIunscaled(self.params[1])),
    transpose := self >> DST6(self.params[1])
));

#F DST8(<n>) - Discrete Sine Transform, Type VIII, non-terminal
#F Definition: (n x n)-matrix [ sin((k+1/2)*(l+1/2)*pi/(n-1/2)) | k,l = 0...n-1 ]
#F Note:       DST8 is symmetric
#F Example:    DST8(8)
Class(DST8, DTTBase, rec(
    terminate := self >> Mat(DST_VIIIunscaled(self.params[1])),
    transpose := self >> Copy(self),
));


# Copyright (c) 2018-2021, Carnegie Mellon University
# See LICENSE for details

# ==========================================================================
# Mon(<perm-func>, <value-diag>, <structural-diag>) = perm * diag
# ==========================================================================
Class(Mon, BaseMat, SumsBase, rec(
    new := (self, perm, vdiag, sdiag) >> Checked(
       domain(perm) = domain(vdiag) and domain(perm) = domain(sdiag),
       SPL(WithBases(self,
		rec( perm := perm,
		     vdiag := vdiag,
		     sdiag := sdiag ))).setDims()),

    #-----------------------------------------------------------------------
    dims := self >> [self.perm.domain(), self.perm.range()],
    setDims := meth(self) self.dimensions := self.dims(); return self; end,
    #-----------------------------------------------------------------------
    sums := self >> self,
    rChildren := self >> [self.perm, self.vdiag, self.sdiag],
    rSetChild := rSetChildFields("perm", "vdiag", "sdiag"),

    children := ~.rChildren,
    setChild := ~.rSetChild,
    child := (self, n) >> Cond(n=1, self.perm, n=2, self.vdiag, n=3, self.sdiag,
	Error("<n> must be in [1..3]")),
    #-----------------------------------------------------------------------
    isPermutation := False,
    #-----------------------------------------------------------------------
    isReal := self >> self.vdiag.isReal(),
    #-----------------------------------------------------------------------
    toAMat := self >> AMatSPL( Gath(self.perm) * Diag(self.vdiag) * Diag(self.sdiag) ),
    #-----------------------------------------------------------------------
    transpose := self >>   # we use inherit to copy all fields of self
        Inherit(self, rec(
		perm := self.perm.transpose(),
		vdiag := fCompose(self.vdiag, self.perm),
		sdiag := fCompose(self.sdiag, self.perm),
		dimensions := Reversed(self.dimensions))),
    #-----------------------------------------------------------------------
    print := (self, i, is) >>
       Print(self.name, "(", self.perm, ", ", self.vdiag, ", ", self.sdiag, ")"),
    #-----------------------------------------------------------------------
    arithmeticCost := (self, costMul, costAddMul) >> Sum(self.vdiag.tolist(), costMul)
));

Declare(SMon, GMon, SGMon);

# ==========================================================================
# GMon(<gath-func>, <value-diag>, <structural-diag>) = diag * gath
# ==========================================================================
Class(GMon, Mon, rec(
    #-----------------------------------------------------------------------
    dims := self >> [domain(self.perm), range(self.perm)],
    #-----------------------------------------------------------------------
    toAMat := self >> AMatSPL( self.vdiag * self.sdiag * Gath(self.perm) ),
    #-----------------------------------------------------------------------
    transpose := self >> SMon(self.perm, self.vdiag, self.sdiag)
    #-----------------------------------------------------------------------
));

# ==========================================================================
# SMon(<scat-func>, <value-diag>, <structural-diag>) = scat * diag
# ==========================================================================
Class(SMon, Mon, rec(
    #-----------------------------------------------------------------------
    dims := self >> [range(self.perm), domain(self.perm)],
    #-----------------------------------------------------------------------
    toAMat := self >> AMatSPL( Scat(self.perm) * self.vdiag * self.sdiag ),
    #-----------------------------------------------------------------------
    transpose := self >> GMon(self.perm, self.vdiag, self.sdiag)
    #-----------------------------------------------------------------------
));

# ==========================================================================
# SGMon(<scat-func>, <gath-func>, <value-diag>, <structural-diag>) = scat * diag * gath
# ==========================================================================
Class(SGMon, Mon, rec(
    new := (self, scat, gath, vdiag, sdiag) >> Checked(
       domain(scat) = domain(vdiag) and domain(vdiag) = domain(sdiag) and
       domain(sdiag) = domain(gath),
       SPL(WithBases(self,
		rec( scat := scat,
		     gath := gath,
		     vdiag := vdiag,
		     sdiag := sdiag ))).setDims()),

    rChildren := self >> [self.scat, self.gath, self.vdiag, self.sdiag],
    rSetChild := rSetChildFields("scat", "gath", "vdiag", "sdiag"),
    child := (self, n) >> Cond(n=1, self.scat,  n=2, self.gath,
	                       n=3, self.vdiag, n=4, self.sdiag,
			       Error("<n> must be in [1..3]")),
    #-----------------------------------------------------------------------
    dims := self >> [range(self.scat), range(self.gath)],
    #-----------------------------------------------------------------------
    toAMat := self >> AMatSPL(Scat(self.scat) * self.vdiag * self.sdiag *
	                      Gath(self.gath)),
    #-----------------------------------------------------------------------
    transpose := self >> SGMon(self.gath, self.scat, self.vdiag, self.sdiag),
    #-----------------------------------------------------------------------
    print := (self, i, is) >>
       Print(self.name, "(", self.scat, ", ", self.gath, ", ", self.vdiag, ", ", self.sdiag, ")")
 ));


################################################################################
##
#W compute_cohomology.gi              GroupTheoretical Package
##
#W Paul Bruillard, Cesar Galindo, Siu-Hung Ng, Julia Plavnik, Eric Rowell,
#W Zhenghan Wang
##
## Installation file for compute_cohomology functions of the GroupTheoretical Package
##
#Y Copyright (C) 2016, Battelle Memorial Institute
##
################################################################################

################################################################################
##
#F get_cohomology(<group>,<int>). . . . a helper function for computing cocycles
##
InstallGlobalFunction(get_cohomology, function(G,k)
  local HkG, s, InitialA, a, OrderedA, A, R, C, CH, H, classes, cocycles, w;

  HkG:=GroupHomology(G,k);;
  if Size(HkG)=0 then s:=2; else s:=Lcm(HkG); fi;;
  InitialA:=CyclicGroup(s);;
  a:=MinimalGeneratingSet(InitialA)[1];;
  OrderedA:=List([1..s],x->a^x);;
  A:=TrivialGModuleAsGOuterGroup(G,InitialA);;
  R:=ResolutionFiniteGroup(G,k+1);;
  C:=HomToGModule(R,A);;
  CH:=CohomologyModule(C,k);;
  H:=ActedGroup(CH);;
  classes:=Elements(H);;
  cocycles:=List(classes,x->Mapping(CH!.representativeCocycle(x)));;
  cocycles:=List(cocycles,x->function(g,h,k) return E(s)^(Position(OrderedA,x(g,h,k))); end);;
  return [H,cocycles];;
end);

#E compute_cohomology.gi . . . . . . . . . . . . . . . . . . . . . . . ends here


# Adjacency function for Kneser-type graphs.
BindGlobal("DisjointSets", function(x, y)
    return Intersection(x, y) = [];
end);

# Adjacency function for doubled Odd and Grassmann graphs.
BindGlobal("SymmetrizedInclusion", function(x, y)
    return x <> y and (IsSubset(x, y) or IsSubset(y, x));
end);

# Adjacency function for roots of E_8.
BindGlobal("RootAdjacency", function(x, y)
    return x*y = 8;
end);

# Adjacency function for forms graphs.
BindGlobal("RankAdjacency",
    r -> function(x, y)
        return RankMat(x-y) in r;
    end);

# Adjacency function for adjacency matrices.
BindGlobal("MatrixAdjacency",
    M -> function(x, y)
        return M[x][y] = 1;
    end);

# Adjacency function for adjacency lists.
BindGlobal("ListAdjacency",
    L -> function(x, y)
        return y in L[x];
    end);

# Point-line incidence
BindGlobal("PointLineIncidence", function(x, y)
    return x in y or y in x;
end);


local reps,tori,labels,I,II,ords;

# Permutation from standard to gap
# / 1 2 3 4 \
# \ 2 4 3 1 /
# Position(pr,Permuted([2,3,4,2],(2,4,1)))=24;

#......Ross Jordan Block Sizes........Donna & Ross nilpotents........(in GAP notation)
reps:=[[[2,1],[4,1],[3,1],[1,1]],    # F_4
       [[2,1],[4,1],[7,1],[5,1]],    # F_4(a_1) [[2,1],[4,1],[10,1],[1,1]],
       [[6,1],[10,1],[1,1],[5,1]],   # F_4(a_2) [[9,1],[1,1],[10,1],[4,1]],
       [[4,1],[3,1],[1,1]],          # C_3      [[1,1],[3,1],[4,1]],
       [[2,1],[4,1],[3,1]],          # B_3      the same
       [[4,1],[6,1],[10,1],[18,1]],  # F_4(a_3) [[4,1],[13,1],[11,1],[12,1]],
       [[4,1],[1,1],[10,1]],         # C_3(a_1) [[1,1],[10,1],[4,1]],
       [[2,1],[3,1],[1,1]],          # sA_2A_1  [[3,1],[1,1],[2,1]],
       [[4,1],[3,1]],                # B_2      the same
       [[2,1],[4,1],[1,1]],          # A_2sA_1  the same
       [[3,1],[1,1]],                # sA_2     the same
       [[2,1],[4,1]],                # A_2      the same
       [[2,1],[3,1]],                # A_1sA_1  [[2,1],[1,1]],
       [[3,1]],                      # sA_1     [[1,1]],
       [[2,1]]];                     # A_1      [[2,1]]

I:=[[],[3],[2,3],[],[],[2,3,1],[3],[],[],[],[],[],[],[],[]];

#
# The parabolic where the representative is distinguished
#
II:=[[1,2,3,4],
     [1,2,3,4],
     [1,2,3,4],
     [4,3,1],
     [2,4,3],
     [1,2,3,4],
     [4,3,1],
     [3,1,2],
     [4,3],
     [2,4,1],
     [3,1],
     [2,4],
     [2,1],
     [1],
     [2]];

labels:=["F_4","F_4(a_1)","F_4(a_2)","C_3","B_3","F_4(a_3)","C_3(a_1)","\\tilde A_2A_1","B_2","A_2\\tilde A_1","\\tilde A_2","A_2","A_1\\tilde A_1","\\tilde A_1","A_1"];

tori:=[
       [2,2,2,2],
       [2,2,0,2],
       [2,0,0,2],
       [2,1,1,0],
       [0,2,0,2],
       [0,0,0,2],
       [0,1,1,0],
       [1,0,0,1],
       [1,2,0,0],
       [0,0,1,0],
       [2,0,0,0],
       [0,2,0,0],
       [0,0,0,1],
       [1,0,0,0],
       [0,1,0,0]
    ];

ords:=[];

return [reps,tori,labels,I,II,ords];