#######################################################################
##  Hecke - specht.gi : the kernel of Hecke                          ##
##                                                                   ##
##     A GAP package for calculating the decomposition numbers of    ##
##     Hecke algebras of type A (over fields of characteristic       ##
##     zero). The functions provided are primarily combinatorial in  ##
##     nature. Many of the combinatorial tools from the (modular)    ##
##     representation theory of the symmetric groups appear in the   ##
##     package.                                                      ##
##                                                                   ##
##     These programs, and the enclosed libraries, are distributed   ##
##     under the usual licensing agreements and conditions of GAP.   ##
##                                                                   ##
##     Dmitriy Traytel                                               ##
##     (heavily using the GAP3-package SPECHT 2.4 by Andrew Mathas)  ##
##                                                                   ##
#######################################################################

## Hecke 1.0: June 2010:
##   - Translated to GAP4

## SPECHT Change log
## 2.4:
##  - fixed more bugs in H.valuation; returned incorrect answers before
##    when e=0 or e=p (symmetric group case).
##  - fixed bug in Dq(), via Sasha Kleshchev.

## 2.3:
##  - fixed bug in H.valuation  reported by Johannes Lipp
##  - fixed bug in Sq() reported by Johannes Lipp.
##  - corrected FindDecompositionMatrix() so that it updates the matrices
##    CrystalMatrices[] and DecompositionMatrices[] after calculating a
##    crystalized decomposition matrix.

## 2.2: June 1996: various changes requested by the referee.
##  - mainly changing function names.
##  - DecompositionMatrix changed so that it no longer attempts to
##    calculate decomposition matrices in the finite field case; added
##    CalculateDecompositionMatrix() to do this.
##  - replaced Matrix() function with MatrixDecompositionMatrix() and
##    added the function DecompositionMatrixMatix().

## 2.1: April 1996:
##  - Added a filename argument to SaveDecompositionMatrix and made it
##    save the non-decomposition matrices under (more) sensible names; the
##    later is done using the existence of a record component d.matname.
##    Need to do something here about reading such matrices in (this can be
##    done using DecompositionMatrix)..
##  - Changed ReadDecompositionMatrix so that it automatically reads the
##    format of version 1.0 files (and deleted ReadOldDecompositionMatrix).
##    Also fixed a bug here which was causing confusion between Hecke algebra
##    and Schur algebra matrices.
##  - Renamed FindDecompositionMatrix as KnownDecompositionMatrix because it
##    doesn't try to calculate a crytalized decomposition matrix; the new
##    FindDecompositionMatrix will return the decomposition matrix if at all
##    possible. In particular, this fixes a 'bug' in SimpleDimension.
##  - Rewrote AdjustmentMatrix so that it actually works.
##  - Changed P()->S() module conversions so that it no longer requires
##    the projective module to have positive coefficients (and fixed bug in
##    matrix ops).

## 2.0: March 1996:
##   - LLT algorithm implemented for calculating crystal basis of
##     the Fock space and hence by specialization the decomposition
##     matrices for all Hecke algebras over fields of characteristic
##     zero; most of the work is done by the internal function Pq().
##     This required a new set of 'module' types ("Pq", "Sq", and "Dq"),
##     with correspondining operation sets H.operations.X. In particular,
##     these include q-induction and q-restriction, effectively allowing
##     computations with $U_q(\hat sl_e)$ on the Fock space.
##   - crystallized decomposition matrices added and decomposition
##     matrix type overhauled. d.d[c] is now a list of records with
##     partitions replaced by references to d.rows etc. Changed format
##     of decomposition matrix library files so as to handle polynomial
##     entries in the matrices..
##   - printing of Specht records changed allowing more compact and
##     readable notation. eg. S(1,1,1,1)->S(1^4). (see SpechtPrintFn).
##   - reversed order of parts and coeffs records in H.S(), d.d etc;
##     these lists are now sets which improves use of Position().
##   - reorganised the Specht function and the record H() which it returns;
##     in particular added H.info.
##   - extended SInducedModule() and SRestrict to allow multiple inducing and
##     restricting (all residues).

## 1.0: December 1995: initial release.

######################################################################

## Here is a description of the structure of the main records used
## in SPECHT
#### In GAP4 all "operations"-fields do not exist. They are replaced
#### by top-level operations with some filter restrictions

## 1. Specht()
## Specht() is the main function in specht.g, it returns a record 'H'
## which represents the family of Hecke algebras, or Schur algebras,
## corresponding to some fixed field <R> and parameter <q>. 'H' has
## the components:
##   IsSpecht      : is either 'true' or 'false' depending on whether
##                   'H' is a Hecke algebra or Schur algebra resp.
#### This function is obsolete in the GAP4 version - it is replaced
#### by the filter IsHecke
##   S(), P(), D() : these three functions return records which
##                   represent Specht modules, PIMs, and simple
##                   'H' modules repectively. These functions exist
##                   only when 'H' is a Hecke algebra record.
#### Use MakeSpecht(H,...) instead of H.S(...)
##   W(), P(), F() : these are the corresponding functions for Weyl
##                   modules, PIMs, and simple modules of Schur algbras.
#### Use MakeSpecht(S,...) instead of S.W(...)
##   info          : this is a record with components
##                     version: SPECHT version number,
##                     Library: path to SPECHT library files
##                     SpechtDirectory: addition directory searched by
##                            SPECHT (defaults to current directory)
##                     Indeterminate: the indedeterminate used by SPECHT
##                            in the LLT algorithm when H.p=0.
##   operations    : apart from the obvious things like the Print()
##                   function for 'H' this record also contains the
##                   operation records for the modules S(), P() etc.
##                   as well as functions for manipulating these records
##                   and accessing decomposition matrix files. The most
##                   most important of these are:
##                     S, P, D, Pq, Sq, Dq : operations records for modules
#### Most functions are now on toplevel
##                     New : creation function for modules. Internally
##                       modules are created via
##                         H.operations.new(module,coeffs,parts)
##                       where module is one of "S", "P", "D", "Sq", "Pq",
##                       or "Dq" ("S" and "D" are used even for Schur
##                       algebras), coeffs is an integer or a *set* of
##                       integers and parts is a partition or a *set* of
##                       partitions. In any programs the use of New is
##                       better than H.S(mu), for example, because the
##                       function names are different for Hecke and Schur
##                       algebras. Note that coeffs and parts must be
##                       ordered reverse lexicographically (ie. they are
##                       *sets*).
#### Use Module(H,...) instead of H.operations.New(...)
##                     Collect: like New() except that  coeffs and parts
##                       need not be sets (and may contain repeats).
#### Use Collect(H,...) instead of H.operations.Collect(...)
##                     NewDecompositionMatrix : creates a decomposition
##                       matrix.
#### Use ReadDecompositionMatrix(H,...) instead of H.operations.ReadDecompositionMatrix(...)
##                     ReadDecompositionMatrix : reads, and returns, a
##                       decomposition matrix file.
#### Use KnownDecompositionMatrix(H,...) instead of H.operations.KnownDecompositionMatrix(...)
##                     KnownDecompositionMatrix : returns a decomposition
##                       matrix of a given size; will either extract this
##                       matrix from Specht's internal lists or call
##                       ReadDecompositionMatrix(), or try to calculate
##                       the decomposition matrix (without using the
##                       crystalized decomposition matrices).
#### Use FindDecompositionMatrix(H,...) instead of H.operations.FindDecompositionMatrix(...)
##                     FindDecompositionMatrix : like KnownDM except that
##                       it will calculate the crystalized DM if needed.
##   Ordering      : a function for ordering partitions; controls how
##                   decomposition matrices for H are printed.
#### Use SetOrdering(...) to control the output
##   e             : order of <q> in <R>
#### Use OrderOfQ(...) to extract the e from an algebra or a module
#### corresponding to an algebra
##   p             : characteristic of <R>
##   valuation     : the valuation map of [JM2]; used primarily by
##                   the q-Schaper theorem.D
##   HeckeRing     : bookkeeping string used primarily in searching for
##                   library files.
##   Pq(), Sq()    : Functions for computing elements of the Fock space
##                   when H.p=0 (used in LLT algorithm). Note that there is
##                   no Dq; also unlike their counter parts S(), P(), and
##                   D() they accept only partitions as arguments.
#### Use MakeFockPIM(H,...) instead of H.Pq(...)
#### Use MakeFockSpecht(H,...) instead of H.Sq(...)
##
## 2. The module functions S(), P() and D() (and Schur equivalents)
## These functions return record 'x' which represents some 'H'--module.
## 'x' is a record with the following components:
##   H      : a pointer back to the corresponding algebra
##   module : one of "S", "P", "D", "Sq", "Pq", or "Dq", (not "W", or "F").
##   coeffs : a *set* of coefficients
##   parts  : the corresponding *set* of partitions
##   operations :
##       + - * / : for algebric manipulations
##       Print : calls PrintModule
##       Coefficient : returns the coefficient of a given partition
#### Use Coefficient(x,...) instead of x.operations.Coefficient(...)
##       PositiveCoefficients : true if all coefficients are non-negative
##       IntegralCoefficients : true if all coefficients are integral
##       InnerProduct : computes the 'Kronecker' inner product
##       Induce, Restrict, SInduce, SRestrict : induction and restriction
##                 functions taylored to 'x'. These functions convert 'x'
##                 to a linear combination of Specht modules, induce and
##                 then convert back to the type of 'x' (if possible).
##                 Quantized versions are applied as appropriate.
##       S, P, D : functions for rewriting 'x' into the specified type
##                 (so, for example, S('x') rewrites 'x' as a linear
##                 combination of Specht modules).
## 'x'.operations is a pointer to 'H'.operations.('x'.module).
##
## 3. DecompositionMatrices
## Decomposition matrices 'd' in Specht are represented as records with the
## following components:
##
##   d : a list, indexed by d.cols, where each entry is a record
##       corresponding to a column of 'd'; this record has components
##       two * sets* coeffs and parts, where parts is the index of the
##       corresponding partition in d.rows.
##   rows : the *set* of the partitions which make up the rows or 'd'.
##   cols : the *set* of the partitions which make up the rows or 'd'.
##   inverse : a list of records containing the inverse of 'd'. These
##          records are computed only as needed.
##   dimensions : a list of the dimensions of the simle modules; again
##          comuted only as needed.
##   IsDecompositionMatrix : false if 'd' is a crystallized decomposition
##          matrix, and true otherwise.
##   H :a pointer back to the corresponding algebra
##   operations :
##     = : equality.
##     Print, TeX, Matrix : printing, TeX, and a GAP Matrix.
##     AddIndecomposable : for adding a PIM to 'd'.
##     Store : for updating Specht's internal record of 'd'.
##     S, P, D: for accessing the entries of 'd' and using 'd' to
##              convert between the various types of 'H'--modules.
##              These are actually records, each containing three
##              functions S(), P(), and D(); so X.Y() tells 'd' how
##              to write an X-module as a linear comination of Y-modules.
##     Invert : calculates D(mu) using 'd'.
##     IsNewIndecomposable : the heart of the 'IsNewIndecomposable'
##              function.
##     Induce : for inducing decomposition matrices (non--crystallized).
##   P : a short-hand for d.H.P('d',<mu>).

###########################################################################

## Specht() is the main function in the package, although in truth it is
## little more than a wrapper for the funcions S(), P(), and D().
## Originally, I had these as external functions, but decided that it
## was better to tie these functions to e=H.e as strongly as possible.
InstallMethod(Specht_GenAlgebra,"generate a type-Algebra object",
  [IsString,IsInt,IsInt,IsFunction,IsString],
  function(type,e,p,valuation,HeckeRing)
    local H;
    if not IsPrime(p) and p<>0
    then Error("Specht(<e>,<p>,<val>), <p> must be a prime number");
    fi;

    H := rec(
      e:=e,
      p:=p,
      valuation:=valuation,
      HeckeRing:=HeckeRing,
      ## bits and pieces about H
      info:=rec(version:=PackageInfo("hecke")[1].Version,
                Library:=Directory(
                  Concatenation(DirectoriesPackageLibrary("hecke")[1]![1],"e",
                  String(e),"/")),
      ## We keep a copy of SpechtDirectory in H so that we have a
      ## chance of finding new decomposition matrices when it changes.
                SpechtDirectory:=Directory(".")),

      ## This record will hold any decomposition matrices which Specht()
      ## (or rather its derivatives) read in. This used to be a public
      ## record; it is now private because q-Schur algebra matrices and
      ## Hecke algebra matrices might need to coexist.
      DecompositionMatrices:=[],

      ## for ordering the rows of the decomposition matrices
      ## (as it is common to all decomposition matrices it lives here)
      Ordering:=Lexicographic,
    );

    if p = 0
    then
      ## This list will hold the crystallized decomposition matrices (p=0)
      H.CrystalMatrices:=[];
      H.Indeterminate:=Indeterminate(Integers);
      SetName(H.Indeterminate,"v");
    else H.Indeterminate:=1;
    fi;

    if type = "Schur" then
      Objectify(SchurType,H);
    else
      Objectify(HeckeType,H);
    fi;

    return H;
  end
);

InstallMethod(Specht_GenAlgebra,"generate a type-Algebra object",
  [IsString,IsInt,IsInt,IsFunction],
  function(type,e,p,val) local ring;
    if not IsPrime(p)
    then Error("Specht(<e>,<p>,<val>), <p> must be a prime number");
    fi;
    if e=p then
      ring:=Concatenation("p",String(p),"sym");
      return Specht_GenAlgebra(type,e,p,val,ring);
    else
      return Specht_GenAlgebra(type,e,p,val,"unknown");
    fi;
  end
);

InstallMethod(Specht_GenAlgebra,"generate a type-Algebra object",
  [IsString,IsInt,IsInt],
  function(type,e,p) local val, ring;
    if not IsPrime(p)
    then Error("Specht(<e>,<p>,<val>), <p> must be a prime number");
    fi;
    if e=p then
      ring:=Concatenation("p",String(p),"sym");
      ## return the exponent of the maximum power of p dividing x
      val:=function(x) local i;
        i:=0;
        while x mod p=0 do
          i:=i+1;
          x:=x/p;
        od;
        return i;
      end;
    else
      ring:=Concatenation("e",String(e), "p",String(p));
      ## return the exponent of the maximum power of p that
      ## divides e^x-1.
      val:=function(x) local i;
        if x mod e=0 then return 0;
        else
          i:=0;
          while x mod p=0 do
            i:=i+1;
            x:=x/p;
          od;
          return p^i;
        fi;
      end;
    fi;
    return Specht_GenAlgebra(type,e,p,val,ring);
  end
);

InstallMethod(Specht_GenAlgebra,"generate a type-Algebra object",
  [IsString,IsInt],
  function(type,e) local val;
      if e=0 then val:=x->x;
      else
        val:=function(x)
          if x mod e = 0 then return 1;
          else return 0;
          fi;
        end;
      fi;
      return Specht_GenAlgebra(type,e,0,val,Concatenation("e",String(e), "p0"));
  end
);

InstallMethod(Specht,"generate a Hecke-Algebra object",[IsInt],
  function(e) return Specht_GenAlgebra("Specht",e); end
);
InstallMethod(Specht,"generate a Hecke-Algebra object",[IsInt,IsInt],
  function(e,p) return Specht_GenAlgebra("Specht",e,p); end
);
InstallMethod(Specht,"generate a Hecke-Algebra object",[IsInt,IsInt,IsFunction],
  function(e,p,val) return Specht_GenAlgebra("Specht",e,p,val); end
);
InstallMethod(Specht,"generate a Hecke-Algebra object",[IsInt,IsInt,IsFunction,IsString],
  function(e,p,val,ring) return Specht_GenAlgebra("Specht",e,p,val,ring); end
);
InstallMethod(Schur,"generate a Schur-Algebra object",[IsInt],
  function(e) return Specht_GenAlgebra("Schur",e); end
);
InstallMethod(Schur,"generate a Schur-Algebra object",[IsInt,IsInt],
  function(e,p) return Specht_GenAlgebra("Schur",e,p); end
);
InstallMethod(Schur,"generate a Schur-Algebra object",[IsInt,IsInt,IsFunction],
  function(e,p,val) return Specht_GenAlgebra("Schur",e,p,val); end
);
InstallMethod(Schur,"generate a Schur-Algebra object",[IsInt,IsInt,IsFunction,IsString],
  function(e,p,val,ring) return Specht_GenAlgebra("Schur",e,p,val,ring); end
);

InstallImmediateMethod(Characteristic, IsAlgebraObj, 0,
  function(H) return H!.p; end
);

InstallImmediateMethod(IsZeroCharacteristic, IsAlgebraObj, 0,
  function(H) return H!.p = 0; end
);

InstallImmediateMethod(OrderOfQ, IsAlgebraObj, 0,
  function(H) return H!.e; end
);

InstallImmediateMethod(OrderOfQ, IsAlgebraObjModule, 0,
  function(x) return x!.H!.e; end
);

InstallMethod(SetOrdering,"writing access to H.Ordering",
  [IsAlgebraObj,IsFunction],
  function(H,ord) H!.Ordering := ord; end
);

InstallMethod(SpechtCoefficients,"reading access to S.coeffs",[IsHeckeSpecht],
  function(S) return S!.coeffs; end
);

InstallMethod(SpechtPartitions,"reading access to S.parts",[IsHeckeSpecht],
  function(S) return S!.parts; end
);

## FORMER TOPLEVEL FUNCTIONS ###################################################
#F Calculates the dimensions of the simple modules in d
## Usage:  SimpleDimension(d)   -> prints all simple dimensions
##         SimpleDimension(H,n) -> prints all again
##         SimpleDimension(H,mu) or SimpleDimension(d,mu) -> dim D(mu)
InstallMethod(SimpleDimensionOp,
  "all simple dimensions from decomposition matrix",[IsDecompositionMatrix],
  function(d) local cols, collabel, M, c, x;
    if IsSchur(d!.H) then
      Print("# SimpleDimension() not implemented for Schur algebras\n");
      return fail;
    fi;
    cols:=StructuralCopy(d!.cols);
    if d!.H!.Ordering=Lexicographic then
      cols:=cols{[Length(cols),Length(cols)-1..1]};
    else Sort(cols, d!.H!.Ordering);
    fi;
    cols:=List(cols, c->Position(d!.cols,c));
    collabel:=List([1..Length(cols)], c->LabelPartition(d!.cols[cols[c]]));
    M:=Maximum(List(collabel, Length))+1;

    for c in [1..Length(cols)] do
      Print(String(collabel[c],-M),": ");
      if IsBound(d!.dimensions[cols[c]]) then
        Print(d!.dimensions[cols[c]],"\n");
      else
        x:=MakeSimpleOp(d,d!.cols[cols[c]]);
        if x=fail then Print("not known\n");
        else
          d!.dimensions[cols[c]]:=Sum([1..Length(x!.parts)],
                             r->x!.coeffs[r]*SpechtDimension(x!.parts[r]));
          Print(d!.dimensions[cols[c]],"\n");
        fi;
      fi;
    od;
    return true;
  end
);

InstallMethod(SimpleDimensionOp,
  "simple dimensions of a partition from decomposition matrix",
  [IsDecompositionMatrix,IsList],
  function(d,mu) local c, x;
    c:=Position(d!.cols,mu);
    if c=fail then
      Print("# SimpleDimension(<d>,<mu>), <mu> is not in <d>.cols\n");
      return fail;
    else
      if not IsBound(d!.dimensions[c]) then
        x:=MakeSimpleOp(d,d!.cols[c]);
        if x=fail then return fail;
        else d!.dimensions[c]:=Sum([1..Length(x!.parts)],
                            r->x!.coeffs[r]*SpechtDimension(x!.parts[r]));
        fi;
      fi;
      return d!.dimensions[c];
    fi;
  end
);

InstallMethod(SimpleDimensionOp,
  "all simple dimensions from algebra",[IsAlgebraObj,IsInt],
  function(H,n) local d;
    d:=FindDecompositionMatrix(H,n);
    if d=fail then
      Print("# SimpleDimension(H,n), the decomposition matrix of H_n is ",
            "not known.\n");
      return fail;
    fi;
    return SimpleDimensionOp(d);
  end
);

InstallMethod(SimpleDimensionOp,
  "simple dimensions of a partition from algebra",[IsAlgebraObj,IsList],
  function(H,mu) local d;
    d:=FindDecompositionMatrix(H,Sum(mu));
    if d=fail then
      Print("# SimpleDimension(H,mu), the decomposition matrix of H_Sum(mu) is",
            " not known.\n");
      return fail;
    fi;
    return SimpleDimensionOp(d,mu);
  end
); # SimpleDimension

#P returns a list of the e-regular partitions occurring in x
InstallMethod(ListERegulars,"e-regular partitions of a module",
  [IsAlgebraObjModule],
  function(x) local e,parts,coeffs,p;
    e:=x!.H!.e;
    parts:=x!.parts;
    coeffs:=x!.coeffs;
    if e=0 then return parts;
    elif x=0*x then return [];
    else return List(Filtered([Length(parts),Length(parts)-1..1],
           p->IsERegular(e,parts[p])),p->[coeffs[p], parts[p]]);
    fi;
  end
); # ListERegulars


##P Print the e-regular partitions in x if IsSpecht(x); on the other hand,
### if IsDecompositionMatrix(x) then return the e-regular part of the
### decompotion marix.
InstallMethod(ERegulars,"e-regular part of the given decomposition matrix",
  [IsDecompositionMatrix],
  function(d) local regs, y, r, len;
    regs:=DecompositionMatrix(d!.H,d!.rows,d!.cols,not IsCrystalDecompositionMatrix(d));
    regs!.d:=[]; #P returns a list of the e-regular partitions occurring in x
    for y in [1..Length(d!.cols)] do
      if IsBound(d!.d[y]) then
        regs!.d[y]:=rec(parts:=[], coeffs:=[]);
        for r in [1..Length(d!.d[y].parts)] do
          len:=Position(d!.cols,d!.rows[d!.d[y].parts[r]]);
          if len<>fail then
            Add(regs!.d[y].parts,len);
            Add(regs!.d[y].coeffs,d!.d[y].coeffs[r]);
          fi;
        od;
      fi;
    od;
    regs!.rows:=regs!.cols;
    return regs;
  end
);

InstallMethod(ERegulars, "print e-regular partitions of a module",
  [IsAlgebraObjModule],
  function(x) local len, regs, y;
    len:=0;
    regs:=ListERegulars(x);
    if regs=[] or IsInt(regs[1]) then Print(regs, "\n");
    else
      for y in regs do
        if (len + 5 + 4*Length(y[2])) > 75 then len:=0; Print("\n"); fi;
        if y[1]<>1 then Print(y[1], "*"); len:=len + 3; fi;
        Print(y[2], "  ");
        len:=len + 5 + 4*Length(y[2]);
      od;
      Print("\n");
    fi;
  end
); # ERegulars

#F Returns true if S(mu)=D(mu) - note that this implies that mu is e-regular
## (if mu is not e-regular, fail is returned).     -- see [JM2]
## IsSimle(H,mu)
##   ** uses H.valuation
InstallMethod(IsSimpleModuleOp,
  "test whether the given partition defines a simple module",
  [IsAlgebraObj,IsList],
  function(H,mu) local mud, simple, r, c, v;
    if not IsERegular(H!.e,mu) then return false;
    elif mu=[] then return true; fi;

    mud:=ConjugatePartition(mu);
    simple:=true; c:=1;
    while simple and c <=mu[1] do
      v:=H!.valuation(mu[1]+mud[c]-c);
      simple:=ForAll([2..mud[c]], r->v=H!.valuation(mu[r]+mud[c]-c-r+1));
      c:=c+1;
    od;
    return simple;
  end
); #IsSimpleModule

#F Split an element up into compontents which have the same core.
## Usage: SplitECores(x) - returns as list of all block components
##        SplitECores(x,lambda) - returns a list with (i) core lambda,
## (ii) the same core as lambda, or (iii) the same core as the first
## element in lambda if IsSpecht(lambda).
InstallMethod(SplitECoresOp,"for a single module",[IsAlgebraObjModule],
  function(x) local cores, c, cpos, y, cmp;
    if x=fail or x=0*x then return []; fi;

    cores:=[]; cmp:=[];
    for y in [1..Length(x!.parts)] do
      c:=ECore(x!.H!.e, x!.parts[y]);
      cpos:=Position(cores, c);
      if cpos=fail then
        Add(cores, c);
        cpos:=Length(cores);
        cmp[cpos]:=[[],[]];
      fi;
      Add(cmp[cpos][1], x!.coeffs[y]);
      Add(cmp[cpos][2], x!.parts[y]);
    od;
    for y in [1..Length(cmp)] do
      cmp[y]:=Module(x!.H,x!.module,cmp[y][1],cmp[y][2]);
    od;
    return cmp;
  end
);

InstallMethod(SplitECoresOp,"for a module and a partition",
  [IsAlgebraObjModule,IsList],
  function(x,mu) local c, cpos, y, cmp;
    c:=ECore(x!.H!.e, mu);
    cmp:=[ [],[] ];
    for y in [1..Length(x!.parts)] do
      if ECore(x!.H!.e, x!.parts[y])=c then
        Add(cmp[1], x!.coeffs[y]);
        Add(cmp[2], x!.parts[y]);
      fi;
    od;
    cmp:=Module(x!.H,x!.module, cmp[1], cmp[2]);
    return cmp;
  end
);

InstallMethod(SplitECoresOp,"for a module and a specht module",
  [IsAlgebraObjModule,IsAlgebraObjModule],
  function(x,s) local c, cpos, y, cmp;
    c:=ECore(s!.H!.e, s!.parts[Length(x!.parts)]);
    cmp:=[ [],[] ];
    for y in [1..Length(x!.parts)] do
      if ECore(x!.H!.e, x!.parts[y])=c then
        Add(cmp[1], x!.coeffs[y]);
        Add(cmp[2], x!.parts[y]);
      fi;
    od;
    cmp:=Module(x!.H,x!.module, cmp[1], cmp[2]);
    return cmp;
  end
); #SplitECores

#F This function returns the image of <mu> under the Mullineux map using
## the Kleshcehev(-James) algorihm, or the supplied decomposition matrix.
## Alternatively, given a "module" x it works out the image of x under
## Mullineux.
## Usage:  MullineuxMap(e|H|d, mu) or MullineuxMap(x)
InstallMethod(MullineuxMapOp,"image of x under Mullineux",[IsAlgebraObjModule],
  function(x) local e, v;
    e := x!.H!.e;
    if x=fail or not IsERegular(e,x!.parts[Length(x!.parts)]) then
      Print("# The Mullineux map is defined only for e-regular partitions\n");
      return fail;
    fi;
    if x=fail or x=0*x then return fail; fi;
    if IsHeckeSpecht(x) then
      if Length(x!.module)=1 then
        return Collect(x!.H,x!.module,x!.coeffs,
                 List(x!.parts, ConjugatePartition));
      else
        v:=x!.H!.info.Indeterminate;
        return Collect(x!.H,x!.module,
             List([1..Length(x!.coeffs)],
                mu->Value(v^-EWeight(e,x!.parts[mu])*x!.coeffs[mu],v^-1)),
             List(x!.parts,ConjugatePartition) );
      fi;
    elif Length(x!.module)=1 then
      return Sum([1..Length(x!.coeffs)],
               mu->Module(x!.H,x!.module,x!.coeffs[mu],
                     MullineuxMap(e,x!.parts[mu])));
    else
      v:=x!.H!.info.Indeterminate;
      return Sum([1..Length(x!.coeffs)],
               mu->Module(x!.H,x!.module,
                     Value(v^-EWeight(e,x!.parts[mu])*x!.coeffs[mu]),
                     MullineuxMap(e,x!.parts[mu])));
    fi;
  end
);

InstallMethod(MullineuxMapOp,"for ints: image of <mu> under the Mullineux map",
  [IsInt,IsList],
  function(e,mu)
    if not IsERegular(e,mu) then                     ## q-Schur algebra
      Error("# The Mullineux map is defined only for e-regular ",
            "partitions\n");
    fi;
    return PartitionGoodNodeSequence(e,
                  List(GoodNodeSequence(e,mu),x->-x mod e));
  end
);

InstallMethod(MullineuxMapOp,
  "for algebras: image of <mu> under the Mullineux map",
  [IsAlgebraObj,IsList],
  function(H,mu)
    return MullineuxMapOp(H!.e,mu);
  end
);

InstallMethod(MullineuxMapOp,
  "for decomposition matrices: image of <mu> under the Mullineux map",
  [IsDecompositionMatrix,IsList],
  function(d,mu) local e, x;
    e := d!.H!.e;
    if not IsERegular(e,mu) then                     ## q-Schur algebra
      Error("# The Mullineux map is defined only for e-regular ",
            "partitions\n");
    fi;
    x:=d!.H!.P(d,mu);
    if x=fail or x=0*x then
      Print("MullineuxMap(<d>,<mu>), P(<d>,<mu>) not known\n");
      return fail;
    else return ConjugatePartition(x!.parts[1]);
    fi;
    return true;
  end
); # MullineuxMap

#F Calculates the Specht modules in sum_{i>0}S^lambda(i) using the
## q-analogue of Schaper's theorem.
## Uses H.valuation.
##   Usage:  Schaper(H,mu);
InstallMethod(SchaperOp,"calculates Specht modules",[IsAlgebraObj,IsList],
  function(H,mu)
    local mud, schaper, hooklen, c, row, r, s, v;

    Sort(mu); mu:=mu{[Length(mu),Length(mu)-1..1]};
    mud:=ConjugatePartition(mu);
    hooklen:=[];
    for r in [1..Length(mu)] do
      hooklen[r]:=[];
      for c in [1..mu[r]] do
        hooklen[r][c]:=mu[r] + mud[c] - r - c + 1;
      od;
    od;

    schaper:=Module(H,"S",0,[]);
    for c in [1..mu[1]] do
      for row in [1..mud[1]] do
        for r in [row+1..mud[1]] do
          if mu[row] >=c and mu[r] >=c then
            v:=H!.valuation(hooklen[row][c])
                  - H!.valuation(hooklen[r][c]);
            if v<>0 then
              s:=AddRimHook(RemoveRimHook(mu,r,c,mud),row,hooklen[r][c]);
              if s<>fail then
                schaper:=schaper+Module(H,"S",
                                    (-1)^(s[2]+mud[c]-r)*v,s[1]);
              fi;
            fi;
          fi;
        od;
      od;
    od;
    return schaper;
  end
);  #Schaper

#F returns the matrix of upper bounds on the entries in the decomposition
## matrix <d> given by the q-Schaper theorem
## *** undocumented
InstallMethod(SchaperMatrix,"upper bounds of entries of a decomposition matrix",
  [IsDecompositionMatrix],
  function(d) local r, C, c, coeff, sh, shmat;
    shmat:=DecompositionMatrix(d!.H,d!.rows,d!.cols,true);
    shmat!.d:=List(shmat!.cols, c->rec(parts:=[],coeffs:=[]));
    C:=Length(d!.cols)+1; ## this keeps track of which column we're up to
    for r in [Length(d!.rows),Length(d!.rows)-1..1] do
      if d!.rows[r] in d!.cols then C:=C-1; fi;
      sh:=Schaper(d!.H,d!.rows[r]);
      for c in [C..Length(d!.cols)] do
        coeff:=InnerProduct(sh,MakePIMSpechtOp(d,d!.cols[c]));
        if coeff<>fail and coeff<>0*coeff then
          Add(shmat!.d[c].parts,r);
          Add(shmat!.d[c].coeffs,coeff);
        fi;
      od;
    od;
    sh:=[];
    for c in [1..Length(d!.d)] do
      Add(shmat!.d[c].parts, Position(shmat!.rows,shmat!.cols[c]));
      Add(shmat!.d[c].coeffs,1);
    od;
    shmat!.matname:="Schaper matrix";
    return shmat;
  end
);

## FORMER DECOMPOSITION MATRICES TOPLEVEL FUNCTIONS ############################
##############################################################
## Next some functions for accessing decomposition matrices ##
##############################################################

#F Returns the dcomposition number d_{mu,nu}; row and column removal
## are used if the projective P(nu) is not already known.
## Usage: DecompositionNumber(H,mu,nu), or
##        DecompositionNumber(d,mu,nu);
## If unable to calculate the decomposition number we return false.
## Note that H.IsSpecht is false if we are looking at decomposition matrices
## of a q-Schur algebra and true for a Hecke algebra.
InstallMethod(DecompositionNumber,
  "for a decomposition matrix and two partitions",
  [IsDecompositionMatrix,IsList,IsList],
  function(d,mu,nu) local Pnu;
    if mu=nu then return 1;
    elif not Dominates(nu,mu) then return 0;
    else
      Pnu:=MakePIMSpechtOp(d,nu);
      if Pnu<>fail then return Coefficient(Pnu,mu); fi;
      return Specht_DecompositionNumber(d!.H,mu,nu);
    fi;
  end
);

InstallMethod(DecompositionNumber,"for an algebra and two partitions",
  [IsAlgebraObj,IsList,IsList],
  function(H,mu,nu) local Pnu;
    Pnu:=MakeSpechtOp(Module(H,"P",1,nu),true);
    if Pnu<>fail then return Coefficient(Pnu,mu); fi;
    if not IsSchur(H) and not IsERegular(H!.e, nu) then
      Error("DecompositionNumber(H,mu,nu), <nu> is not ",H!.e,"-regular");
    fi;
    return Specht_DecompositionNumber(H,mu,nu);
  end
);

InstallMethod(Specht_DecompositionNumber,
  "internal: for an algebra and two partitions",
  [IsAlgebraObj,IsList,IsList],
  function(H,mu,nu) local Pnu, RowAndColumnRemoval;

    ## Next we try row and column removal (James, Theorem 6.18)
    ## (here fn is either the identity or conjugation).
    RowAndColumnRemoval:=function(fn) local m,n,i,d1,d2;
      ## x, mu, and nu as above

      mu:=fn(mu); nu:=fn(nu);

      m:=0; n:=0; i:=1;
      while i<Length(nu) and i<Length(mu) do
        m:=m+mu[i]; n:=n+nu[i];
        if m=n then
          d2:=DecompositionNumber(H, fn(mu{[i+1..Length(mu)]}),
                     fn(nu{[i+1..Length(nu)]}));
          if d2=0 then return d2;
          elif IsInt(d2) then
            d1:=DecompositionNumber(H, fn(mu{[1..i]}),fn(nu{[1..i]}));
            if IsInt(d1) then return d1*d2; fi;
          fi;
        fi;
        i:=i+1;
      od;
      return fail;
    end;

    Pnu:=RowAndColumnRemoval(a->a);
    if Pnu=fail then Pnu:=RowAndColumnRemoval(ConjugatePartition); fi;
    return Pnu;
  end
);

#F Returns a list of those e-regular partitions mu such that Px-P(mu)
## has positive coefficients (ie. those partitions mu such that P(mu)
## could potentially split off Px). Simple minded, but useful.
InstallMethod(Obstructions,"for a decomposition matrix and a module",
  [IsDecompositionMatrix,IsAlgebraObjModule],
  function(d,Px) local obs, mu, Pmu, possibles;
    obs:=[];
    if not IsSchur(d!.H) then
      possibles:=Filtered(Px!.parts, mu->IsERegular(Px!.H!.e, mu));
    else possibles:=Px!.parts;
    fi;
    for mu in possibles do
      if mu<>Px!.parts[Length(Px!.parts)] then
        Pmu:=MakePIMSpechtOp(d,mu);
        if Pmu=fail or PositiveCoefficients(Px-Pmu) then Add(obs,mu); fi;
      fi;
    od;
    return obs{[Length(obs),Length(obs)-1..1]};
  end
);

## Interface to d.operations.IsNewDecompositionMatrix. Returns true
## if <Px> contains an indecomposable not listed in <d> and false
## otherwise. Note that the value of <Px> may well be changed by
## this function. If the argument <mu> is used then we assume
## that all of the decomposition numbers down given by <Px> down to
## <mu> are correct. Note also that if d is the decomposition matrix
## for H(Sym_{r+1}) then the decomposition matrix for H(Sym_r) is passed
## to IsNewDecompositionMatrix.
##   Usage: IsNewIndecomposable(<d>,<Px> [,<mu>]);
## If <mu> is not supplied then we set mu:=true; this
## turns on the message printing in IsNewIndecomposable().
InstallMethod(IsNewIndecomposableOp,
  "for a decomposition matrix and a module",
  [IsDecompositionMatrix,IsAlgebraObjModule],
  function(d,Px) local oldd;
    oldd:=FindDecompositionMatrix(d!.H,Sum(d!.rows[1])-1);
    return IsNewIndecomposableOp(d!.H,d,Px,oldd,[]);
  end
);

InstallMethod(IsNewIndecomposableOp,
  "for a decomposition matrix, a module and a partition",
  [IsDecompositionMatrix,IsAlgebraObjModule,IsList],
  function(d,Px,mu) local oldd;
    oldd:=FindDecompositionMatrix(d!.H,Sum(d!.rows[1])-1);
    return IsNewIndecomposableOp(d!.H,d,Px,oldd,mu);
  end
); # IsNewIndecomposable (toplevel)

##P Removes the columns for <Px> in <d>
InstallMethod(RemoveIndecomposableOp,
  "for a decomposition matrix and a partition",
  [IsDecompositionMatrix,IsList],
  function(d,mu) local r, c;
    c:=Position(d!.cols, mu);
    if c=fail then
      Print("RemoveIndecomposable(<d>,<mu>), <mu> is not listed in <d>\n");
    else Unbind(d!.d[c]);
    fi;
  end
); # RemoveIndecomposable

### Prints a list of the indecomposable missing from d
InstallMethod(MissingIndecomposables,
  "missing entries of a decomposition matrix",
  [IsDecompositionMatrix],
  function(d) local c, missing;
    missing:=List([1..Length(d!.cols)], c->not IsBound(d!.d[c]) );
    if true in missing then
      Print("The following projectives are missing from <d>:\n  ");
      for c in [Length(missing),Length(missing)-1..1] do
        if missing[c] then Print("  ", d!.cols[c]); fi;
      od;
      Print("\n");
    fi;
  end
); # MissingIndecomposables

## When no ordering is supplied then rows are ordered first by length and
## then lexicographically. The rows and columns may also be explicitly
## assigned.
## Usage:
##   DecompositionMatrix(H, n [,ordering]);
##   DecompositionMatrix(H, <file>) ** force Specht() to read <file>
InstallOtherMethod(DecompositionMatrix,"for an algebra and an integer",
  [IsAlgebraObj,IsInt],
  function(H,n) local Px, d, c;
    d:=FindDecompositionMatrix(H,n);

    if d=fail then
      if H!.p>0 and n>2*H!.e then  ## no point even trying
        Print("# This decomposition matrix is not known; use ",
              "CalculateDecompositionMatrix()\n# or ",
              "InducedDecompositionMatrix() to calculate with this matrix.",
              "\n");
        return d;
      fi;
      if not IsSchur(H) then c:=ERegularPartitions(H!.e,n);
      else c:=Partitions(n);
      fi;
      d:=DecompositionMatrix(H,Partitions(n),c,true);
    fi;
    if ForAny([1..Length(d!.cols)],c->not IsBound(d!.d[c])) then
      for c in [1..Length(d!.cols)] do
        if not IsBound(d!.d[c]) then
          Px:=MakeSpechtOp(Module(H,"P",1,d!.cols[c]),true);
          if Px<>fail then AddIndecomposable(d,Px,false);
          else Print("# Projective indecomposable P(",
                     TightStringList(d!.cols[c]),") not known.\n");
          fi;
        fi;
      od;
      Store(d,n);
    fi;
    if d<>fail then   ## can't risk corrupting the internal matrix lists
      d:=CopyDecompositionMatrix(d);
    fi;
    return d;
  end
);

InstallOtherMethod(DecompositionMatrix,
  "for an algebra, an integer and an ordering",
  [IsAlgebraObj,IsInt,IsFunction],
  function(H,n,ord)
    H!.Ordering := ord;
    return DecompositionMatrix(H,n);
  end
);

InstallOtherMethod(DecompositionMatrix,"for an algebra and a filename",
  [IsAlgebraObj,IsString],
  function(H,file) local d;
    d:=ReadDecompositionMatrix(H,file,false);
    if d<>fail and not IsBound(d!.matname) then ## override and copy
      Store(d,Sum(d!.cols[1]));
      MissingIndecomposables(d);
    fi;
    if d<>fail then   ## can't risk corrupting the internal matrix lists
      d:=CopyDecompositionMatrix(d);
    fi;
    return d;
  end
);

InstallOtherMethod(DecompositionMatrix,
  "for an algebra, a filename and an ordering",
  [IsAlgebraObj,IsString,IsFunction],
  function(H,file,ord)
      H!.Ordering := ord;
    return DecompositionMatrix(H,file);
  end
); # DecompositionMatrix

#F Tries to calulcate the decomposition matrix d_{H,n} from scratch.
## At present will return only those column indexed by the partitions
## of e-weight less than 2.
InstallMethod(CalculateDecompositionMatrix,"for an algebra and an integer",
  [IsAlgebraObj,IsInt],
  function(H,n) local d, c, Px;
    if not IsSchur(H) then c:=ERegularPartitions(H!.e,n);
    else c:=Partitions(n);
    fi;
    d:=DecompositionMatrix(H,Partitions(n),c,true);
    for c in [1..Length(d!.cols)] do
      if not IsBound(d!.d[c]) then
        Px:=MakeSpechtOp(Module(H,"P",1,d!.cols[c]),true);
        if Px<>fail then AddIndecomposable(d,Px,false);
         else Print("# Projective indecomposable P(",
                    TightStringList(d!.cols[c]),") not known.\n");
        fi;
      fi;
    od;
    return d;
  end
); # CalculateDecompositionMatrix

#F Returns a crystallized decomposition matrix
InstallMethod(CrystalDecompositionMatrix,"for an algebra and an integer",
  [IsAlgebraObj,IsInt],
  function(H,n) local d, Px, c;
    if not IsZeroCharacteristic(H) or IsSchur(H) then
      Error("Crystal decomposition matrices are defined only ",
		         "for Hecke algebras\n         with H!.p=0\n");
    fi;

    d:=ReadDecompositionMatrix(H,n,true);
    if d<>fail then d:=CopyDecompositionMatrix(d);
    else d:=DecompositionMatrix(H,
                Partitions(n),ERegularPartitions(H!.e,n),false);
    fi;
    for c in [1..Length(d!.cols)] do
      if not IsBound(d!.d[c]) then
        AddIndecomposable(d,FindPq(H,d!.cols[c]),false);
      fi;
    od;
    return d;
  end
);

InstallMethod(CrystalDecompositionMatrix,
  "for an algebra, an integer and an ordering",
  [IsAlgebraObj,IsInt,IsFunction],
  function(H,n,ord)
    H!.Ordering := ord;
    return CrystalDecompositionMatrix(H,n);
  end
); # CrystalDecompositionMatrix

## Given a decomposition matrix induce it to find as many columns as
## possible of the next higher matrix using simple minded induction.
## Not as simple minded as it was originally, as it now tries to use
## Schaper's theorem [JM2] to break up troublesome projectives. The
## function looks deceptively simple because all of the work is now
## done by IsNewIndecomposable().
## Usage: InducedDecompositionMatrix(dn)
## in the second form new columns are added to d{n+1}.
InstallMethod(InducedDecompositionMatrix,"induce from decomposition matrix",
  [IsDecompositionMatrix],
  function(d)
    local newd, mu, nu, Px, Py, n,r;

    if IsCrystalDecompositionMatrix(d)
    then Error("InducedDecompositionMatrix(d): ",
                 "<d> must be a decomposition matrix.");
    fi;

    n:=Sum(d!.rows[1])+1;
    if n>8 then                            ## print dots to let the user
      PrintTo("*stdout*","# Inducing.");   ## know something is happening.
    fi;

    nu:=Partitions(n);
    if IsSchur(d!.H) then
      newd:=DecompositionMatrix(d!.H, nu, nu, true);
    else newd:=DecompositionMatrix(d!.H, nu,
                ERegularPartitions(d!.H!.e,n),true);
    fi;

    ## add any P(mu)'s with EWeight(mu)<=1 or P(mu)=S(mu) <=> S(mu')=D(mu')
    for mu in newd!.cols do
      if EWeight(d!.H!.e,mu)<=1 then
       AddIndecomposable(newd,
           MakeSpechtOp(Module(d!.H,"P",1,mu),true),false);
      elif IsSimpleModule(d!.H,ConjugatePartition(mu)) then
        AddIndecomposable(newd, Module(d!.H,"S",1,mu),false);
      fi;
    od;

    ## next we r-induce all of the partitions in d so we can just add
    ## them up as we need them later.
    ## (note that this InducedModule() is Specht()'s and not the generic one)
    d!.ind:=List(d!.rows, mu->List([0..d!.H!.e-1],
              r->RInducedModule(d!.H,Module(d!.H,"S",1,mu),d!.H!.e,r)));

    if n<9 then n:=Length(d!.cols)+1; fi; ## fudge for user friendliness

    for mu in [1..Length(d!.cols)] do
      if IsBound(d!.d[mu]) then
        for r in [1..d!.H!.e] do   ## really the e-residues; see ind above
          ## Here we calculate InducedModule(P(mu),H.e,r).
          Px:=Sum([1..Length(d!.d[mu].parts)],
                     nu->d!.d[mu].coeffs[nu]*d!.ind[d!.d[mu].parts[nu]][r]);
          if IsNewIndecomposableOp(d!.H,newd,Px,d,[fail]) then
            if IsERegular(Px!.H!.e,Px!.parts[Length(Px!.parts)]) then
              # can apply MullineuxMap
              nu:=ConjugatePartition(Px!.parts[1]);
              if nu<>MullineuxMap(d!.H!.e,Px!.parts[Length(Px!.parts)]) then
                ## wrong image under the Mullineux map
                BUG("Induce", 7, "nu = ", nu, ", Px = ", Px);
              else   ## place the Mullineux image of Px as well
                AddIndecomposable(newd,MullineuxMap(Px),false);
              fi;
            fi;
            AddIndecomposable(newd,Px,false);
          fi;
        od;
        if mu mod n = 0 then PrintTo("*stdout*",".");fi;
      fi;
    od;
    Unbind(d!.ind); Unbind(d!.simples); ## maybe we should leave these.

    if n>8 then Print("\n"); fi;
    MissingIndecomposables(newd);
    return newd;
  end
); # InducedDecompositionMatrix

#F Returns the inverse of (the e-regular part of) d. We invert the matrix
## 'by hand' because the matrix routines can't handle polynomial entries.
## This should be much faster than it is???
InstallMethod(InvertDecompositionMatrix,"for a decomposition matrix",
  [IsDecompositionMatrix],
  function(d) local inverse, c, r;
    inverse:=DecompositionMatrix(d!.H,d!.cols,d!.cols,
                                          not IsCrystalDecompositionMatrix(d));

    ## for some reason I can't put this inside the second loop (deleting
    ## the first because d.inverse is not updated this way around...).
    for c in [1..Length(inverse!.cols)] do
      Invert(d,d!.cols[c]);
    od;
    for c in [1..Length(inverse!.cols)] do
      if IsBound(d!.inverse[c]) then
        inverse!.d[c]:=rec(parts:=[], coeffs:=[]);
        for r in [1..c] do
          if IsBound(d!.inverse[r]) and c in d!.inverse[r].parts then
            Add(inverse!.d[c].parts,r);
            Add(inverse!.d[c].coeffs,
                d!.inverse[r].coeffs[Position(d!.inverse[r].parts,c)]);
          fi;
        od;
        if inverse!.d[c]=rec(parts:=[], coeffs:=[]) then Unbind(inverse!.d[c]); fi;
      fi;
    od;
    inverse!.matname:="Inverse matrix";
    return inverse;
  end
); # InvertDecompositionMatrix

#P Saves a full decomposition matrix; actually, only the d, rows, and cols
## records components are saved and the rest calculated when read back in.
## The decomposition matrices are saved in the following format:
##   A_Specht_Decomposition_Matrix:=rec(
##   d:=[[r1,...,rk,d1,...dk],[...],...[]],rows:=[..],cols:=[...]);
## where r1,...,rk are the rows in the first column with corresponding
## decomposition numbers d1,...,dk (if di is a polynomial then it is saved
## as a list [di.valuation,<sequence of di.coffcients]; in particular we
## don't save the polynomial name).
## Usage: SaveDecompositionMatrix(<d>)
##    or  SaveDecompositionMatrix(<d>,<filename>);
InstallMethod(SaveDecompositionMatrix,
  "for a decomposition matrix and a filename",
  [IsDecompositionMatrix,IsString],
  function(d,file)
    local TightList,n,SaveDm,size, r, c, str, tmp;

    n:=Sum(d!.rows[1]);

    size:=SizeScreen();    ## SizeScreen(0 shouldn't affect PrintTo()
    SizeScreen([80,40]);  ## but it does; this is our protection.

    TightList:=function(list) local l, str;
      str:="[";
      for l in list{[1..Length(list)-1]} do
        if IsList(l) then
          l:=TightList(l);
          str:=Concatenation(str,l);
        else str:=Concatenation(str,String(l));
        fi;
        str:=Concatenation(str,",");
      od;
      l:=list[Length(list)];
      if IsList(l) then
        l:=TightList(l);
        str:=Concatenation(str,l);
      else str:=Concatenation(str,String(l));
      fi;
      return Concatenation(str,"]");
    end;

    if d=fail then Error("SaveDecompositionMatrix(<d>), d=fail!!!\n"); fi;

    SaveDm:=function(file)
      AppendTo(file,"## This is a GAP library file generated by \n## Hecke ",
            d!.H!.info.version, "\n\n## This file contains ");
      if IsBound(d!.matname) then
        AppendTo(file,"a(n) ", d!.matname, " for n = ", Sum(d!.rows[1]),"\n");
      else
        if IsCrystalDecompositionMatrix(d) then AppendTo(file,"the crystallized "); fi;
        AppendTo(file,"the decomposition matrix\n## of the ");
        if not IsSchur(d!.H) then
          if d!.H!.e<>d!.H!.p then AppendTo(file,"Hecke algebra of ");
          else AppendTo(file,"symmetric group ");
          fi;
        else AppendTo(file,"q-Schur algebra of ");
        fi;
        AppendTo(file,"Sym(",n,") over a field\n## ");
        if d!.H!.p=0 then AppendTo(file,"of characteristic 0 with ");
        elif d!.H!.p=d!.H!.e then AppendTo(file,"of characteristic ",d!.H!.p,".\n\n");
        else AppendTo(file,"with HeckeRing = ", d!.H!.HeckeRing, ", and ");
        fi;
        if d!.H!.p<>d!.H!.e then AppendTo(file,"e=", d!.H!.e, ".\n\n");fi;
      fi;

      AppendTo(file,"A_Specht_Decomposition_Matrix:=rec(\nd:=[");
      str:="[";
      for c in [1..Length(d!.cols)] do
        if not IsBound(d!.d[c]) then AppendTo(file,str,"]");
        else
          for r in d!.d[c].coeffs do
            if IsLaurentPolynomial(r) then
            tmp:=ShallowCopy(CoefficientsOfLaurentPolynomial(r));
            AppendTo(file,str,"[",tmp[2],",",TightStringList(tmp[1]),"]");
            else AppendTo(file,str,r);
            fi;
            str:=",";
          od;
          for r in d!.d[c].parts do
            AppendTo(file,str,r);
          od;
          AppendTo(file,"]");
          str:=",[";
        fi;
      od;
      AppendTo(file,"],rows:=",TightList(d!.rows));
      AppendTo(file,",cols:=",TightList(d!.cols));
      if IsCrystalDecompositionMatrix(d) then
        AppendTo(file,",crystal:=true");
      fi;
      if IsBound(d!.matname) then AppendTo(file,",matname:=\"",d!.matname,"\""); fi;
      AppendTo(file,");\n");
    end;

    ## the actual saving of d
    InfoRead1("#I* ", "SaveDecompositionMatrix( \"",
              file, "\")\n");
    SaveDm(file);

    ## now we put d into DecompositionMatrices
    if not IsBound(d!.matname) then Store(d,n); fi;

    SizeScreen(size); # restore screen.
  end
);

InstallMethod(SaveDecompositionMatrix,"for a decomposition matrix",
  [IsDecompositionMatrix],
  function(d) local n,file;
    if d!.H!.HeckeRing="unknown" then
      Print("SaveDecompositionMatrix(d): \n     the base ring of the Hecke ",
            "algebra is unknown.\n     You must set <d>.H.HeckeRing in ",
            "order to save <d>.\n");
      return;
    fi;

    n:=Sum(d!.rows[1]);

    if IsBound(d!.matname) then
      file:=Concatenation(d!.H!.HeckeRing,".",d!.matname{[1]},String(n));
    elif not IsCrystalDecompositionMatrix(d) then
      file:=Concatenation(d!.H!.HeckeRing,".",String(n));
    else  ## crystallized decomposition matrix
      file:=Concatenation("e", String(d!.H!.e), "crys.", String(n));
    fi;

    SaveDecompositionMatrix(d,file);
  end
);# SaveDecompositionMatrix()

#F Returns the 'adjustment matrix' [J] for <d> and <dp>. ie the
## matrix <a> such that <dp>=<a>*<d>.
InstallMethod(AdjustmentMatrix,"for two decomposition matrices",
  [IsDecompositionMatrix,IsDecompositionMatrix],
  function(dp,d) local ad, c, x;
    if d!.cols<>dp!.cols or d!.rows<>dp!.rows then return fail; fi;

    ad:=DecompositionMatrix(dp!.H,dp!.cols,dp!.cols,true);
    ad!.matname:="Adjustment matrix";
    c:=1;
    while ad<>fail and c<=Length(d!.cols) do
      if IsBound(dp!.cols[c]) then
        x:=MakePIMSpechtOp(dp, dp!.cols[c]);
        x!.H:=d!.H;
        x:=MakePIMOp(d,x);
        if x=fail then ad:=fail;
        else AddIndecomposable(ad,x,false);
        fi;
      fi;
      c:=c+1;
    od;
    return ad;
  end
); # AdjustmentMatrix

## Returns the a GAP matrix for the decomposition matrix <d>. Note that
## the rows and columns and <d> are ordered according to H.info.Ordering.
InstallMethod(MatrixDecompositionMatrix,"decomposition matrix -> matrix",
  [IsDecompositionMatrix],
  function(d) local r,c, rows, cols, m;
    rows:=StructuralCopy(d!.rows);
    if d!.H!.Ordering<>Lexicographic then
      Sort(rows,d!.H!.Ordering);
      rows:=List(rows,r->Position(d!.rows,r));
    else rows:=[Length(rows),Length(rows)-1..1];
    fi;
    cols:=StructuralCopy(d!.cols);
    if d!.H!.Ordering<>Lexicographic then
      Sort(cols,d!.H!.Ordering);
      cols:=List(cols,r->Position(d!.cols,r));
    else cols:=[Length(cols),Length(cols)-1..1];
    fi;
    m:=[];
    for r in [1..Length(rows)] do
      m[r]:=[];
      for c in [1..Length(cols)] do
        if IsBound(d!.d[cols[c]]) and rows[r] in d!.d[cols[c]].parts then
          m[r][c]:=d!.d[cols[c]].coeffs[Position(d!.d[cols[c]].parts,rows[r])];
        else m[r][c]:=0;
        fi;
      od;
    od;
    return m;
  end
);

## Given a GAP matrix this function returns a Specht decomposition matrix.
##   H = Specht() record
##   m = matrix: either #reg x #reg, #parts x #reg, or #parts x #parts
##   n = Sym(n)
InstallMethod(DecompositionMatrixMatrix,"matrix -> decomposition matrix",
  [IsAlgebraObj,IsMatrix,IsInt],
  function(H,m,n) local r, c, rows, cols, d;
    rows:=Partitions(n);
    cols:=ERegularPartitions(H,n);
    if Length(rows)<>Length(m) then rows:=cols; fi;
    if Length(cols)<>Length(m[1]) then cols:=rows; fi;
    if Length(rows)<>Length(m) or Length(cols)<>Length(m[1]) then
       Print("# usage: DecompositionMatrixMatrix(H, m, n)\n",
             "   where m is a matrix of an appropriate size.\n");
       return fail;
    fi;
    if ForAll(m, r->ForAll(r, c->IsInt(c) )) then
      d:=DecompositionMatrix(H,StructuralCopy(rows),StructuralCopy(cols),true);
    else  ## presumably crystalized
      d:=DecompositionMatrix(H,StructuralCopy(rows),StructuralCopy(cols),false);
    fi;
    ## now we order the rows and columns properly
    if H!.Ordering<>Lexicographic then Sort(rows, H!.Ordering);
    else rows:=rows{[Length(rows),Length(rows)-1..1]};
    fi;
    rows:=List(d!.rows, r->Position(rows, r) );
    if H!.Ordering<>Lexicographic then Sort(cols, H!.Ordering);
    else cols:=cols{[Length(cols),Length(cols)-1..1]};
    fi;
    cols:=List(d!.cols, c->Position(cols, c) );
    for c in [1..Length(cols)] do
       d!.d[c]:=rec(parts:=[], coeffs:=[]);
       for r in [1..Length(rows)] do
         if m[rows[r]][cols[c]]<>0*m[rows[r]][cols[c]] then ## maybe polynomial
           Add(d!.d[c].parts, r);
           Add(d!.d[c].coeffs, m[rows[r]][cols[c]]);
         fi;
       od;
       if d!.d[c].parts=[] then Unbind(d!.d[c]); fi;
    od;
    return d;
  end
); # DecompositionMatrixMatrix

## OPERATIONS OF FORMER SPECHT RECORD ##########################################

## The following two functions are used by P(), and elsewhere.
##   generate the hook (k,1^n-k)  - as a list - where k=arg
##   actually not quite a hook since if arg is a list (n,k1,k2,...)
##   this returns (k1,k2,...,1^(n-sum k_i))
InstallMethod(HookOp,"for an integer and a list of lists",[IsInt,IsList],
  function(n,K) local k, i;
    k:=Sum(K);
    if k < n then Append(K, List([1..(n-k)], i->1));
    elif k > n then Error("hook, partition ", k, " bigger than ",n, "\n");
    fi;
    return K;
  end
); #Hook

InstallMethod(DoubleHook,"for four integers",[IsInt,IsInt,IsInt,IsInt],
  function(n,x,y,a) local s, i;
    s:=[x];
    if y<>0 then Add(s,y); fi;
    if a<>0 then Append(s, List([1..a],i->2)); fi;
    i:=Sum(s);
    if i < n then
      Append(s, List([1..n-i], i->1));
      return s;
    elif i=n then return s;
    else return [];
    fi;
  end
); #DoubleHook

#### RENAMED Omega -> HeckeOmega
## Returns p(n) - p(n-1,1) + p(n-2,1^2) - ... + (-1)^(n-1)*p(1^n).
## So, S(mu)*Omega(n) is the linear combination of the S(nu)'s where
## nu is obtained by wrapping an n-hook onto mu and attaching the
## sign of the leg length.
InstallMethod(HeckeOmega,"for an algebra, a string and an integer",
  [IsAlgebraObj,IsString,IsInt],
  function(H,module,n)
    return Module(H,module,List([1..n],x->(-1)^(x)),
                     List([1..n],x->Hook(n,x)));
  end
); #HeckeOmega

## MODULES #####################################################################
InstallMethod(Module,"create new module",[IsAlgebraObj,IsString,IsList,IsList],
  function(H,m,c,p)
    local module;
    module := rec(H:=H,module:=m,coeffs:=c,parts:=p);

    if m = "S" and not IsSchur(H) then Objectify(HeckeSpechtType,module);
    elif m = "P" and not IsSchur(H) then Objectify(HeckePIMType,module);
    elif m = "D" and not IsSchur(H) then Objectify(HeckeSimpleType,module);
    elif m = "Sq" and not IsSchur(H) then Objectify(HeckeSpechtFockType,module);
    elif m = "Pq" and not IsSchur(H) then Objectify(HeckePIMFockType,module);
    elif m = "Dq" and not IsSchur(H) then Objectify(HeckeSimpleFockType,module);
    elif m = "W" or m = "S" then Objectify(SchurWeylType,module); module!.module:="W";
    elif m = "P" then Objectify(SchurPIMType,module);
    elif m = "F" or m = "D" then Objectify(SchurSimpleType,module); module!.module:="F";
    elif m = "Wq" or m = "Sq" then Objectify(SchurWeylFockType,module); module!.module:="Wq";
    elif m = "Pq" then Objectify(SchurPIMFockType,module);
    elif m = "Fq" or m = "Dq" then Objectify(SchurSimpleFockType,module); module!.module:="Fq";
    fi;

    return module;
  end
);
InstallTrueMethod(IsFockModule,IsFockSpecht);
InstallTrueMethod(IsFockModule,IsFockPIM);
InstallTrueMethod(IsFockModule,IsFockSimple);
InstallTrueMethod(IsSchurModule,IsSchurWeyl);
InstallTrueMethod(IsSchurModule,IsSchurPIM);
InstallTrueMethod(IsSchurModule,IsSchurSimple);
InstallTrueMethod(IsFockSchurModule,IsFockSchurWeyl);
InstallTrueMethod(IsFockSchurModule,IsFockSchurPIM);
InstallTrueMethod(IsFockSchurModule,IsFockSchurSimple);
InstallTrueMethod(IsSchurModule,IsFockSchurModule);
InstallTrueMethod(IsSchurWeyl,IsFockSchurWeyl);
InstallTrueMethod(IsSchurPIM,IsFockSchurPIM);
InstallTrueMethod(IsSchurSimple,IsFockSchurSimple);

InstallMethod(Module,"create new module",[IsAlgebraObj,IsString,IsInt,IsList],
  function(H,m,c,p)
    return Module(H,m,[c],[p]);
  end
);

InstallMethod(Module,"create new module",[IsAlgebraObj,IsString,IsLaurentPolynomial,IsList],
  function(H,m,c,p)
    return Module(H,m,[c],[p]);
  end
);

## Takes two lists, one containing coefficients and the other the
## corresponding partitions, and orders them lexicogrphcailly collecting
## like terms on the way. We use a variation on quicksort which is
## induced by the lexicographic order (if parts contains partitions of
## different integers this can lead to an error - which we don't catch).
InstallMethod(Collect,"utility for module generation",
  [IsAlgebraObj,IsString,IsList,IsList],
  function(H, module, coeffs, parts)
    local newx, i, Place, Unplace, places;

    ## inserting parts[i] into places. if parts[i]=[p1,p2,...] then
    ## we insert it into places at places[p1][[p2][...] stopping
    ## at the fist empty position (say places[p1], or places[p1][p2]
    ## etc.). Here we are trying to position parts[i] at
    ## places[p1]...[pd]. Actually, we just insert i rather than
    ## parts[i] and if we find that parts[i]=parts[j] for some j then
    ## we set coeffs[i]:=coeffs[i]+coeffs[j] and don't place j.
    Place:=function(i, places, d) local t;
      if IsInt(places) then
        t:=places;
        if parts[t]=parts[i] then coeffs[t]:=coeffs[t]+coeffs[i];
        else
          places:=[];
          places[parts[t][d]]:=t;
          if parts[i][d]<>parts[t][d] then places[parts[i][d]]:=i;
          else places:=Place(i, places, d);
          fi;
        fi;
      elif places=[] or not IsBound(places[parts[i][d]]) then
        # must be a list
        places[parts[i][d]]:=i;
      else places[parts[i][d]]:=Place(i, places[parts[i][d]], d+1);
      fi;
      return places;
    end;

    Unplace:=function(places) local p, newp, np;
      newp:=[[],[]];
      for p in places do
        if IsInt(p) then if coeffs[p]<>0*coeffs[p] then
          Add(newp[1], coeffs[p]);
          Add(newp[2], StructuralCopy(parts[p])); fi;
        else
          np:=Unplace(p);
          Append(newp[1], np[1]);
          Append(newp[2], np[2]);
        fi;
      od;
      return newp;
    end;

   if parts=[] then return Module(H,module,0,[]);
   elif Length(parts)=1 then return Module(H,module,coeffs,parts);
   else places:=[];
      for i in [1..Length(parts)] do places:=Place(i, places, 1); od;
      newx:=Unplace(places);
      if newx=[[],[]] then return Module(H,module,0,[]);
      else return Module(H,module,newx[1],newx[2]);
      fi;
    fi;
  end  ## H.operations.Collect
);

## MODULE CONVERSION ###########################################################

## Finally the conversion functions S(), P() and D(). All take
## a linear combination of Specht modules and return corresponding
## linear combinations of Specht, indecomposables, and simples resp.
## If they have a problem they return false and print an error
## message unless silent=true.
InstallMethod(MakeSpechtOp,"S()->S()",[IsHeckeSpecht,IsBool],
  function(x,silent) return x; end
);

InstallMethod(MakeSpechtOp,"S[q]()->S[q]()",[IsDecompositionMatrix,IsHeckeSpecht],
  function(d,x) return x; end
);

## Here I only allow for linear combinations of projectives which
## have non-negative coefficients; the reason for this is that I
## can't see how to do it otherwise. The problem is that in the
## Grothendieck ring there are many ways to write a given linear
## combination of Specht modules (or PIMs).
InstallMethod(MakePIMOp,"S()->P()",[IsHeckeSpecht,IsBool],
  function(x,silent) local proj, tmp;
    if x=fail or x=0*x then return x;
    elif x!.parts=[[]] then return Module(x!.H,"P",x!.coeffs[1],[]);
    fi;

    proj:=Module(x!.H,"P",0,[]);
    while x<>fail and x<>0*x and
    ( IsSchur(x!.H) or IsERegular(x!.H!.e,x!.parts[Length(x!.parts)]) ) do
      proj:=proj+Module(x!.H,"P",x!.coeffs[Length(x!.parts)],
                                      x!.parts[Length(x!.parts)]);
      tmp:=MakeSpechtOp(
                Module(x!.H,"P",-x!.coeffs[Length(x!.parts)],
                                      x!.parts[Length(x!.parts)]),true);
      if tmp<>fail then x:=x+tmp; else x:=fail; fi;
    od;
    if x=fail or x<>0*x then
      if not silent then
        Print("# P(<x>), unable to rewrite <x> as a sum of projectives\n");
      fi;
    else return proj;
    fi;
    return fail;
  end
);

InstallMethod(MakePIMOp,"S[q]()->P[q]()",[IsDecompositionMatrix,IsHeckeSpecht],
  function(d,x) local nx, r, c, P, S;
    if x=fail or x=0*x then return x; fi;
    if IsCrystalDecompositionMatrix(d) then P:="Pq"; S:="Sq";
    else P:="P"; S:="S";
    fi;

    nx:=Module(x!.H,P,0,[]);
    while x<>fail and x<>0*x do
      c:=Position(d!.cols,x!.parts[Length(x!.parts)]);
      if c=fail or not IsBound(d!.d[c]) then return fail; fi;
      nx:=nx+Module(x!.H,P,x!.coeffs[Length(x!.parts)],d!.cols[c]);
      x:=x+Module(x!.H,S,-x!.coeffs[Length(x!.parts)]*d!.d[c].coeffs,
                            List(d!.d[c].parts,r->d!.rows[r]));
    od;
    return nx;
  end
);

InstallMethod(MakeSimpleOp,"S()->D()",[IsHeckeSpecht,IsBool],
  function(x,silent) local y, d, simples, r, c;
    if x=fail or x=0*x then return x;
    elif x!.parts=[[]] then return Module(x!.H,"D",x!.coeffs[1],[]);
    fi;

    d:=KnownDecompositionMatrix(x!.H,Sum(x!.parts[1]));
    if d<>fail then
      y:=MakeSimpleOp(d,x);
      if y<>fail then return y; fi;
    fi;

    ## since that didn't work, we use the LLT algorithm when IsBound(H.Pq)
    if IsZeroCharacteristic(x!.H) and not IsSchur(x!.H) then
      return Sum([1..Length(x!.parts)],
                 r->x!.coeffs[r]*Specialized(FindSq(x!.H,x!.parts[r])));
    fi;

    # next, see if we can calculate the answer.
    d:=Concatenation(x!.H!.HeckeRing,"D");
    # finally, we can hope that only partitions of e-weight<2 appear in x
    r:=1; simples:=Module(x!.H,"D",0,[]);
    while simples<>fail and r <= Length(x!.parts) do
      if IsSimpleModule(x!.H, x!.parts[r]) then
        simples:=simples+Module(x!.H,"D",x!.coeffs[r], x!.parts[r]);
      elif IsERegular(x!.H!.e,x!.parts[r]) and EWeight(x!.H!.e,x!.parts[r])=1
      then
        y:=Module(x!.H,"S",1,ECore(x!.H!.e,x!.parts[r]))
                           * HeckeOmega(x!.H,"S",x!.H!.e);
        c:=Position(y!.parts,x!.parts[r]); ## >1 since not IsSimpleModule
        simples:=simples
                  +Module(x!.H,"D",[1,1],[y!.parts[c],y!.parts[c-1]]);
      #### elif IsBound(x.operations.(d)) then ## FIXME not needed anymore?
      ####   simples:=simples+x.operations.(d)(x.parts[r]);
      else simples:=fail;
      fi;
      r:=r+1;
    od;
    if simples=fail and not silent then
      Print("# D(<x>), unable to rewrite <x> as a sum of simples\n");
      return fail;
    else return simples;
    fi;
  end
);

InstallMethod(MakeSimpleOp,"S[q]()->D[q]()",[IsDecompositionMatrix,IsHeckeSpecht],
  function(d,x) local nx, y, r, rr, c, D, core;
    if x=fail or x=0*x then return x; fi;
    if IsCrystalDecompositionMatrix(d) then D:="Dq"; else D:="D"; fi;

    nx:=Module(x!.H,D,0,[]);
    for y in [1..Length(x!.parts)] do
      r:=Position(d!.rows, x!.parts[y]);
      if r=fail then return fail; fi;
      core:=ECore(x!.H!.e,x!.parts[y]);
      c:=Length(d!.cols);
      while c>0 and d!.cols[c]>=x!.parts[y] do
        if IsBound(d!.d[c]) then
          rr:=Position(d!.d[c].parts,r);
          if rr<>fail then nx:=nx+Module(x!.H,D,
                         x!.coeffs[y]*d!.d[c].coeffs[rr],d!.cols[c]);
          fi;
        elif ECore(x!.H!.e,d!.cols[c])=core then return fail;
        fi;
        c:=c-1;
      od;
    od;
    return nx;
  end
);

## The P->S functions are quite involved.

#F Writes x, which is a sum of indecomposables, as a sum of S(nu)'s if
## possible. We first check to see if the decomposition matrix for x is
## stored somewhere, and if not we try to calculate what we need. If we
## can't do this we return false.
InstallMethod(MakeSpechtOp,"P()->S()",[IsHeckePIM,IsBool],
  function(x,silent) local y, c, d, mu, specht;
    if x=fail or x=0*x then return x;
    elif x!.parts=[[]] then return Module(x!.H,"S",x!.coeffs[1],[]);
    fi;
    d:=KnownDecompositionMatrix(x!.H,Sum(x!.parts[1]));
    if d<>fail then
      y:=MakeSpechtOp(d,x);
      if y<>fail then return y; fi;
    fi;

    ## since that didn't work, we use the LLT algorithm when
    ## IsBound(H.Pq)
    if IsZeroCharacteristic(x!.H) then
      if not IsSchur(x!.H) or ForAll(x!.parts, c->IsERegular(x!.H!.e,c)) then
        return Sum([1..Length(x!.parts)],c->
                 x!.coeffs[c]*Specialized(FindPq(x!.H,x!.parts[c])));
      fi;
    fi;

    d:=Concatenation(x!.H!.HeckeRing,"S");
    mu:=1; specht:=Module(x!.H,"S",0,[]);
    while specht<>fail and mu<=Length(x!.parts) do
      if IsSimpleModule(x!.H,ConjugatePartition(x!.parts[mu])) then
        specht:=specht+Module(x!.H,"S",1,x!.parts[mu]);
      elif EWeight(x!.H!.e,x!.parts[mu])=1 then ## wrap e-hooks onto c
        c:=Module(x!.H,"S",1,ECore(x!.H!.e, x!.parts[mu]))
                 * HeckeOmega(x!.H,"S",x!.H!.e);
        y:=Position(c!.parts, x!.parts[mu]);
        specht:=specht+Module(x!.H,"S",[1,1],
                                        [c!.parts[y-1],c!.parts[y]]);
      #### elif IsBound(H.operations.P.(d)) then ## FIXME not needed anymore?
      ####   specht:=specht+x.H.operations.P.(d)(x.parts[mu]);
      else specht:=fail;
      fi;
      mu:=mu+1;
    od;
    if specht<>fail then return specht;
    elif not silent then
      Print("# P(<x>), unable to rewrite <x> as a sum of specht modules\n");
    fi;
    return fail;
  end
);

InstallMethod(MakeSpechtOp,"P[q]()->S[q]()",[IsDecompositionMatrix,IsHeckePIM],
  function(d,x) local S, nx, y, r, c;
    if x=fail or x=0*x then return x; fi;
    if IsCrystalDecompositionMatrix(d) then S:="Sq"; else S:="S"; fi;

    nx:=Module(x!.H,S,0,[]);
    for y in [1..Length(x!.parts)] do
      c:=Position(d!.cols,x!.parts[y]);
      if c=fail or not IsBound(d!.d[c]) then return fail; fi;
      nx:=nx+Module(x!.H,S,x!.coeffs[y]*d!.d[c].coeffs,
                              List(d!.d[c].parts,r->d!.rows[r]));
    od;
    return nx;
  end
);

InstallMethod(MakePIMOp,"P()->P()",[IsHeckePIM,IsBool],
  function(x,silent) return x; end
);

InstallMethod(MakePIMOp,"P[q]()->P[q]()",[IsDecompositionMatrix,IsHeckePIM],
  function(d,x) return x; end
);

InstallMethod(MakeSimpleOp,"P()->D()",[IsHeckePIM,IsBool],
    function(x,silent)
      x:=MakeSpechtOp(x,silent);
      if x=fail then return x;
      else return MakeSimpleOp(x,silent);
      fi;
    end
);

InstallMethod(MakeSimpleOp,"P[q]()->D[q]()",[IsDecompositionMatrix,IsHeckePIM],
  function(d,x)
    x:=MakeSpechtOp(d,x);
    if x=fail then return x;
    else return MakeSimpleOp(d,x);
    fi;
  end
);

#F Writes D(mu) as a sum of S(nu)'s if possible. We first check to see
## if the decomposition matrix for Sum(mu) is stored in the library, and
## then try to calculate it directly. If we are unable to do this either
## we return fail.
InstallMethod(MakeSpechtOp,"D()->S()",[IsHeckeSimple,IsBool],
  function(x,silent) local c, d, y, a;
    if x=fail or x=0*x then return x;
    elif x!.parts=[[]] then return Module(x!.H,"S",x!.coeffs[1],[]);
    fi;

    ## look for the decomposition matrix
    d:=KnownDecompositionMatrix(x!.H,Sum(x!.parts[1]));
    if d<>fail then
      y:=MakeSpechtOp(d,x);
      if y<>fail then return y; fi;
    fi;

    ## since that didn't work, we use the LLT algorithm when IsBound(H.Pq)
    if IsZeroCharacteristic(x!.H) and not IsSchur(x!.H) then
      return Sum([1..Length(x!.parts)],
                   c->x!.coeffs[c]*Specialized(FindDq(x!.H,x!.parts[c])));
    fi;

    #### ## Next, see if we can calculate it. ## FIXME not needed anymore?
    #### d:=Concatenation(H.HeckeRing, "S");
    #### if IsBound(H.operations.D.(d)) then
    ####  return H.operations.D.(d)(x,silent);
    #### fi;

    ## Finally, hope only e-weights<2 are involved.
    c:=1; d:=true; y:=Module(x!.H,"S",0,[]);
    while d and c<=Length(x!.parts) do
      if IsSimpleModule(x!.H, x!.parts[c]) then
        y:=y+Module(x!.H,"S",x!.coeffs[c],x!.parts[c]);
      elif IsERegular(x!.H!.e, x!.parts[c]) and EWeight(x!.H!.e,x!.parts[c])=1
      then ## wrap e-hooks onto c
        a:=Module(x!.H,"S",1,ECore(x!.H!.e,x!.parts[c]))
             * HeckeOmega(x!.H,"S",x!.H!.e);
        a!.parts:=a!.parts{[1..Position(a!.parts, x!.parts[c])]};
        a!.coeffs:=a!.coeffs{[1..Length(a!.parts)]}*(-1)^(1+Length(a!.parts));
        y:=y+a;
      else d:=fail;
      fi;
      c:=c+1;
    od;
    if d<>fail then return y;
    elif not silent then
      Print("# Unable to calculate D(mu)\n");
    fi;
    return fail;
  end
);

InstallMethod(MakeSpechtOp,"D[q]()->S[q]()",[IsDecompositionMatrix,IsHeckeSimple],
  function(d,x) local S, nx, y, c, inv;
    if x=fail or x=0*x then return x; fi;
    if IsCrystalDecompositionMatrix(d) then S:="Sq"; else S:="S"; fi;

    nx:=Module(x!.H,S,0,[]);
    for y in [1..Length(x!.parts)] do
      c:=Position(d!.cols,x!.parts[y]);
      if c=fail then return fail; fi;
      inv:=Invert(d,x!.parts[y]);
      if inv=fail then return inv;
      else nx:=nx+x!.coeffs[y]*inv;
      fi;
    od;
    return nx;
  end
);

InstallMethod(MakePIMOp,"D()->P()",[IsHeckeSimple,IsBool],
  function(x,silent)
      x:=MakeSpechtOp(x,silent);
      if x=fail then return x;
      else return MakePIMOp(x,silent);
      fi;
    end
);

InstallMethod(MakePIMOp,"D[q]()->P[q]()",[IsDecompositionMatrix,IsHeckeSimple],
  function(d,x)
    x:=MakeSimpleOp(d,x);
    if x=fail then return x;
    else return MakePIMOp(d,x);
    fi;
  end
);

InstallMethod(MakeSimpleOp,"D()->D()",[IsHeckeSimple,IsBool],
  function(x,silent) return x; end
);

InstallMethod(MakeSimpleOp,"D[q]()->D[q]()",[IsDecompositionMatrix,IsHeckeSimple],
  function(d,x) return x; end
);

## Finally, change the various conversion functions X()->Y();
## in fact, we only have to change the four non-trivial ones:
##   P() <-> S() <-> D().

InstallMethod(MakePIMOp,"Sq()->Pq()",[IsFockSpecht,IsBool],
  function(x,silent) local proj;
    if x=fail or x=0*x then return x;
    elif x!.parts=[[]] then return Module(x!.H,"Pq",x!.coeffs[1],[]);
    fi;

    proj:=Module(x!.H,"Pq",0,[]);
    while x<>0*x and PositiveCoefficients(x) do
      proj:=proj+Module(x!.H,"Pq",x!.coeffs[Length(x!.parts)],
                                       x!.parts[Length(x!.parts)]);
      x:=x-x!.coeffs[Length(x!.parts)]*FindPq(x!.H,x!.parts[Length(x!.parts)]);
    od;
    if x=0*x then return proj;
    elif not silent then
      Print("# P(<x>), unable to rewrite <x> as a sum of projectives\n");
    fi;
    return fail;
  end
);

InstallMethod(MakeSimpleOp,"Sq()->Dq()",[IsFockSpecht,IsBool],
  function(x,silent) local mu;
    if x=fail or x=0*x then return x;
    elif x!.parts=[[]] then return Module(x!.H,"Dq",x!.coeffs[1],[]);
    fi;
    return Sum([1..Length(x!.parts)],
      mu->x!.coeffs[mu]*FindSq(x!.H,x!.parts[mu]) );
  end
);

InstallMethod(MakeSpechtOp,"Pq()->Sq()",[IsFockPIM,IsBool],
  function(x,silent) local mu;
    if x=fail or x=0*x then return x;
    elif x!.parts=[[]] then return Module(x!.H,"Sq",x!.coeffs[1],[]);
    fi;

    return Sum([1..Length(x!.parts)],
      mu->x!.coeffs[mu]*FindPq(x!.H,x!.parts[mu]) );
  end
);

InstallMethod(MakeSpechtOp,"Dq()->Sq()",[IsFockPIM,IsBool],
  function(x,silent) local mu;
    if x=fail or x=0*x then return x;
    elif x!.parts=[[]] then return Module(x!.H,"Sq",x!.coeffs[1],[]);
    fi;

    return Sum([1..Length(x!.coeffs)],
      mu->x!.coeffs[mu]*FindDq(x!.H,x!.parts[mu]) );
  end
);

## Make<module> now also plays the role of H.<module> functions
InstallMethod(MakeSpechtOp,"H.S(mu)",[IsAlgebraObj,IsList],
  function(H,mu) local z;
    if mu = [] then return Module(H,"S",1,[]);
    else
      if not ForAll(mu,z->IsInt(z)) then return fail; fi;
      z:=StructuralCopy(mu);
      Sort(mu, function(a,b) return a>b;end); # non-increasing
      if mu<>z then
        Print("## S(mu), warning <mu> is not a partition.\n");
      fi;
      if Length(mu)>0 and mu[Length(mu)]<0 then
        Error("## S(mu): <mu> contains negative parts.\n");
      fi;
      z:=Position(mu,0);
      if z<>fail then mu:=mu{[1..z-1]}; fi;  ## remove any zeros from mu
    fi;
    return Module(H,"S", 1, mu);
  end
);

InstallMethod(MakeSpechtOp,"H.S(d,mu)",[IsDecompositionMatrix,IsList],
  function(d,mu) local z;
    if mu = [] then return Module(d!.H,"S",1,[]);
    else
      if not ForAll(mu,z->IsInt(z)) then return fail; fi;
      z:=StructuralCopy(mu);
      Sort(mu, function(a,b) return a>b;end); # non-increasing
      if mu<>z then
        Print("## S(mu), warning <mu> is not a partition.\n");
      fi;
      if Length(mu)>0 and mu[Length(mu)]<0 then
        Error("## S(mu): <mu> contains negative parts.\n");
      fi;
      z:=Position(mu,0);
      if z<>fail then mu:=mu{[1..z-1]}; fi;  ## remove any zeros from mu
    fi;
    return MakeSimpleOp(d,Module(d!.H,"S", 1, mu));
  end
);

InstallMethod(MakePIMOp,"H.P(mu)",[IsAlgebraObj,IsList],
  function(H,mu) local z;
    if mu = [] then return Module(H,"P",1,[]);
    else
      if not ForAll(mu,z->IsInt(z)) then return fail; fi;
      z:=StructuralCopy(mu);
      Sort(mu, function(a,b) return a>b;end); # non-increasing
      if mu<>z then
        Print("## P(mu), warning <mu> is not a partition.\n");
      fi;
      if Length(mu)>0 and mu[Length(mu)]<0 then
        Error("## P(mu): <mu> contains negative parts.\n");
      fi;
      z:=Position(mu,0);
      if z<>fail then mu:=mu{[1..z-1]}; fi;  ## remove any zeros from mu
    fi;
    return Module(H,"P", 1, mu);
  end
);

InstallMethod(MakePIMOp,"H.P(d,mu)",[IsDecompositionMatrix,IsList],
  function(d,mu) local z;
    if mu = [] then return Module(d!.H,"P",1,[]);
    else
      if not ForAll(mu,z->IsInt(z)) then return fail; fi;
      z:=StructuralCopy(mu);
      Sort(mu, function(a,b) return a>b;end); # non-increasing
      if mu<>z then
        Print("## P(mu), warning <mu> is not a partition.\n");
      fi;
      if Length(mu)>0 and mu[Length(mu)]<0 then
        Error("## P(mu): <mu> contains negative parts.\n");
      fi;
      z:=Position(mu,0);
      if z<>fail then mu:=mu{[1..z-1]}; fi;  ## remove any zeros from mu
    fi;
    if not IsSchur(d!.H) and not IsERegular(d!.H!.e,mu) then
      Error("P(mu): <mu>=[",TightStringList(mu),
              "] must be ", d!.H!.e,"-regular\n\n");
    fi;
    return MakeSpechtOp(d,Module(d!.H,"P", 1, mu));
  end
);

InstallMethod(MakeSimpleOp,"H.D(mu)",[IsAlgebraObj,IsList],
  function(H,mu) local z;
    if mu = [] then return Module(H,"D",1,[]);
    else
      if not ForAll(mu,z->IsInt(z)) then return fail; fi;
      z:=StructuralCopy(mu);
      Sort(mu, function(a,b) return a>b;end); # non-increasing
      if mu<>z then
        Print("## D(mu), warning <mu> is not a partition.\n");
      fi;
      if Length(mu)>0 and mu[Length(mu)]<0 then
        Error("## D(mu): <mu> contains negative parts.\n");
      fi;
      z:=Position(mu,0);
      if z<>fail then mu:=mu{[1..z-1]}; fi;  ## remove any zeros from mu
    fi;
    if not IsSchur(H) and not IsERegular(H!.e,mu) then
      Error("D(mu): <mu>=[",TightStringList(mu),
              "] must be ", H!.e,"-regular\n\n");
    fi;
    return Module(H,"D", 1, mu);
  end
);

InstallMethod(MakeSimpleOp,"H.D(d,mu)",[IsDecompositionMatrix,IsList],
  function(d,mu) local z;
    if mu = [] then return Module(d!.H,"D",1,[]);
    else
      if not ForAll(mu,z->IsInt(z)) then return fail; fi;
      z:=StructuralCopy(mu);
      Sort(mu, function(a,b) return a>b;end); # non-increasing
      if mu<>z then
        Print("## D(mu), warning <mu> is not a partition.\n");
      fi;
      if Length(mu)>0 and mu[Length(mu)]<0 then
        Error("## D(mu): <mu> contains negative parts.\n");
      fi;
      z:=Position(mu,0);
      if z<>fail then mu:=mu{[1..z-1]}; fi;  ## remove any zeros from mu
    fi;
    if not IsSchur(d!.H) and not IsERegular(d!.H!.e,mu) then
      Error("D(mu): <mu>=[",TightStringList(mu),
              "] must be ", d!.H!.e,"-regular\n\n");
    fi;
    return MakeSpechtOp(d,Module(d!.H,"D", 1, mu));
  end
);

InstallMethod(MakeSpechtOp,"H.S(x)",[IsAlgebraObjModule],
  function(x) return MakeSpechtOp(x,false); end
);

InstallMethod(MakePIMOp,"H.P(x)",[IsAlgebraObjModule],
  function(x) return MakePIMOp(x,false); end
);

InstallMethod(MakeSimpleOp,"H.D(x)",[IsAlgebraObjModule],
  function(x) return MakeSimpleOp(x,false); end
);

#a lazy helper
InstallMethod(MakePIMSpechtOp,"mu->P(mu)->S(mu)",[IsDecompositionMatrix,IsList],
  function(d,mu)
    return MakeSpechtOp(d,Module(d!.H,"P",1,mu));
  end
);

InstallMethod(MakeFockSpechtOp,"H.Sq(mu)",[IsAlgebraObj,IsList],
  function(H,mu) local z;
    if not ForAll(mu,z->IsInt(z)) then
      Error("usage: H.Sq(<mu1,mu2,...>)\n");
    fi;
    z:=StructuralCopy(mu);
    Sort(mu, function(a,b) return a>b;end); # non-increasing
    if mu<>z then
      Print("## Sq(mu), warning <mu> is not a partition.\n");
    fi;
    if Length(mu)>0 and mu[Length(mu)]<0 then
      Error("## B Sq(mu): <mu> contains negative parts.\n");
    fi;
    z:=Position(mu,0);
    if z<>fail then mu:=mu{[1..z-1]}; fi;  ## remove any zeros from mu
    return Module(H,"Sq",1,mu);
  end
);

InstallMethod(MakeFockPIMOp,"H.Pq(mu)",[IsAlgebraObj,IsList],
  function(H,mu) local z;
    if not ForAll(mu,z->IsInt(z)) then
      Error("usage: H.Pq(<mu1,mu2,...>)\n");
    fi;
    z:=StructuralCopy(mu);
    Sort(mu, function(a,b) return a>b;end); # non-increasing
    if mu<>z then
      Print("## Pq(mu), warning <mu> is not a partition.\n");
    fi;
    if Length(mu)>0 and mu[Length(mu)]<0 then
      Error("## B Pq(mu): <mu> contains negative parts.\n");
    fi;
    z:=Position(mu,0);
    if z<>fail then mu:=mu{[1..z-1]}; fi;  ## remove any zeros from mu
    if not IsERegular(H!.e,mu) then
          Error("Pq(mu): <mu>=[",TightStringList(mu),
                "] must be ", H!.e,"-regular\n\n");
    else return FindPq(H,mu);
    fi;
  end
);

## ARITHMETICS #################################################################
InstallMethod(\=,"compare modules",[IsAlgebraObjModule,IsAlgebraObjModule],
  function(a,b) return a!.H=b!.H and a!.module=b!.module
    and Length(a!.parts)=Length(b!.parts) and Length(a!.coeffs)=Length(b!.coeffs)
    and ForAll(Zip(a!.coeffs,a!.parts),a->a in Zip(b!.coeffs,b!.parts)); end
);

InstallMethod(\+,"add modules",[IsAlgebraObjModule,IsAlgebraObjModule],
  function(a,b)
    local i, j, ab, x;

    if a=fail or b=fail then return fail;
    elif a=0*a then return b;
    elif b=0*b then return a;
    elif a!.H<>b!.H then
      Error("modules belong to different Grothendieck rings");
    fi;

    if a!.module<>b!.module then # only convert to Specht modules if different
      if Length(a!.module) <> Length(b!.module) then
        Error("AddModule(<a>,<b>): can only add modules of same type.");
      fi;
      a:=MakeSpechtOp(a,false);
      b:=MakeSpechtOp(b,false);
      if a=fail or b=fail then return fail; fi;
    fi;

    ## profiling shows _convincingly_ that the method used below to add
    ## a and b is faster than using SortParallel or H.operations.Collect.
    ab:=[[],[]];
    i:=1; j:=1;
    while i <=Length(a!.parts) and j <=Length(b!.parts) do
      if a!.parts[i]=b!.parts[j] then
        x:=a!.coeffs[i]+b!.coeffs[j];
        if x<>0*x then
          Add(ab[1],x);
          Add(ab[2], a!.parts[i]);
        fi;
        i:=i+1; j:=j+1;
      elif a!.parts[i] < b!.parts[j] then
        if a!.coeffs[i]<>0*a!.coeffs[i] then
          Add(ab[1], a!.coeffs[i]);
          Add(ab[2], a!.parts[i]);
        fi;
        i:=i+1;
      else
        if b!.coeffs[j]<>0*b!.coeffs[j] then
          Add(ab[1], b!.coeffs[j]);
          Add(ab[2], b!.parts[j]);
        fi;
        j:=j+1;
      fi;
    od;
    if i <=Length(a!.parts) then
      Append(ab[1], a!.coeffs{[i..Length(a!.coeffs)]});
      Append(ab[2], a!.parts{[i..Length(a!.parts)]});
    elif j <=Length(b!.parts) then
      Append(ab[1], b!.coeffs{[j..Length(b!.coeffs)]});
      Append(ab[2], b!.parts{[j..Length(b!.parts)]});
    fi;
    if ab=[[],[]] then ab:=[ [0],[[]] ]; fi;
    return Module(a!.H, a!.module, ab[1], ab[2]);
  end
); # AddModules

InstallMethod(\-,[IsAlgebraObjModule,IsAlgebraObjModule],
  function(a,b)
    if a=fail or b=fail then return fail;
    else
      b:=Module(b!.H, b!.module, -b!.coeffs, b!.parts);
      return a+b;
    fi;
  end
); # SubModules

InstallMethod(\*,"multiply module by scalar",[IsScalar,IsAlgebraObjModule],
  function(n,b)
    if n = 0
    then return Module(b!.H, b!.module, 0, []);
    else return Module(b!.H, b!.module, n*b!.coeffs, b!.parts);
    fi;
  end
);

InstallMethod(\*,"multiply module by scalar",[IsAlgebraObjModule,IsScalar],
  function(a,n)
    if n = 0
    then return Module(a!.H, a!.module, 0, []);
    else return Module(a!.H, a!.module, n*a!.coeffs, a!.parts);
    fi;
  end
);

InstallMethod(\*,"multiply specht modules",[IsHeckeSpecht,IsHeckeSpecht],
  function(a,b) local x, y, ab, abcoeff, xy, z;
    if a=fail or b=fail then return fail;
    elif a!.H<>b!.H then
      Error("modules belong to different Grothendieck rings");
    fi;
    #a:=MakeSpechtOp(a,false);
    ab:=[[],[]];
    for x in [1..Length(a!.parts)] do
      for y in [1..Length(b!.parts)] do
        abcoeff:=a!.coeffs[x]*b!.coeffs[y];
        if abcoeff<>0*abcoeff then
          z:=LittlewoodRichardsonRule(a!.parts[x], b!.parts[y]);
          Append(ab[1], List(z, xy->abcoeff));
          Append(ab[2], z);
        fi;
      od;
    od;
    if ab=[] then return Module(a!.H, a!.module, 0, []);
    else return Collect(b!.H, b!.module, ab[1], ab[2]);
    fi;
  end
);

InstallMethod(\*,"multiply projective indecomposable modules",[IsHeckePIM,IsHeckePIM],
  function(a,b) local x, nx;
    x:=MakeSpechtOp(a,false) * MakeSpechtOp(b,false);
    nx:=MakePIMOp(x,true);
    if nx<>fail then return nx; else return x; fi;
  end
);

InstallMethod(\*,"multiply simple modules",[IsHeckeSimple,IsHeckeSimple],
  function(a,b) local x, nx;
    x:=MakeSpechtOp(a,false) * MakeSpechtOp(b,false);
    nx:=MakeSimpleOp(x,true);
    if nx<>fail then return nx; else return x; fi;
  end
);

InstallMethod(\*,"multiply modules",[IsAlgebraObjModule,IsAlgebraObjModule],
  function(a,b)
    return MakeSpechtOp(a,false) * MakeSpechtOp(b,false);
  end
); # MulModules

InstallMethod(\/,"divide module by scalar",[IsAlgebraObjModule,IsScalar],
  function(b,n) local x;
    if n=0 then Error("can't divide by 0!\n");
    else return Module(b!.H, b!.module, b!.coeffs/n, b!.parts);
    fi;
  end
); # DivModules

################################################################################

InstallMethod(InnerProduct,"inner product of modules",
  [IsAlgebraObjModule,IsAlgebraObjModule],
  function(a,b) local pr, x, y;
    if a=0*a or b=0*b then return 0;
    elif a!.module<>b!.module then
      a:=MakeSpechtOp(a,true);
      b:=MakeSpechtOp(b,true);
    fi;

    pr:=0; x:=1; y:=1;  # use the fact that a.parts and b.parts are ordered
    while x <=Length(a!.parts) and y <=Length(b!.parts) do
      if a!.parts[x]=b!.parts[y] then
        pr:=pr + a!.coeffs[x]*b!.coeffs[y];
        x:=x + 1; y:=y + 1;
      elif a!.parts[x]<b!.parts[y] then x:=x + 1;
      else y:=y + 1;
      fi;
    od;
    return pr;
  end
);  # InnerProduct

#F Returns the Coefficient of p in x
InstallMethod(CoefficientOp, "extract coefficient of a partition from module",
  [IsAlgebraObjModule,IsList],
  function(x,p) local pos;
    pos:=Position(x!.parts, p);
    if pos=fail then return 0;
    else return x!.coeffs[pos];
    fi;
  end
);  # Coefficient

#F Returns true if all coefficients are non-negative
InstallMethod(PositiveCoefficients, "test if all coefficients are non-negative",
  [IsAlgebraObjModule],
  function(x) local c;
    return ForAll(x!.coeffs, c->c>=0);
  end
);

InstallMethod(PositiveCoefficients,
  "test if all coefficients of a Fock module are non-negative",
  [IsFockModule],
  function(x)
    return ForAll(x!.coeffs, p->( IsInt(p) and p>=0 ) or
                      ForAll(CoefficientsOfLaurentPolynomial(p)[1], c->c>=0) );
  end
); # PositiveCoefficients

#F Returns true if all coefficients are integral
InstallMethod(IntegralCoefficients, "test if all coefficients are integral",
  [IsAlgebraObjModule],
  function(x) local c;
    return ForAll(x!.coeffs, c->IsInt(c));
  end
);

InstallMethod(IntegralCoefficients,
  "test if all coefficients of a Fock module are non-negative",
  [IsFockModule],
  function(x)
    return ForAll(x!.coeffs, p-> ( IsInt(p) and p>=0 ) or
                  ForAll(CoefficientsOfLaurentPolynomial(p)[1], c->IsInt(c)) );
  end
); # IntegralCoefficients

## INDUCTION AND RESTRICTION ###################################################

## The next functions are for restricting and inducing Specht
## modules. They all assume that their arguments are indeed Specht
## modules; conversations are done in H.operations.X.Y() as necessary.

## r-induction: on Specht modules:
InstallMethod(RInducedModuleOp, "r-induction on specht modules",
  [IsAlgebraObj,IsHeckeSpecht,IsInt,IsInt],
  function(H, a, e, r) local ind, x, i, j, np;
    ind:=[[],[]];
    for x in [1..Length(a!.parts)] do
      for i in [1..Length(a!.parts[x])] do
        if (a!.parts[x][i] + 1 - i) mod e=r then
          if i=1 or a!.parts[x][i-1] > a!.parts[x][i] then
            np:=StructuralCopy(a!.parts[x]);
            np[i]:=np[i]+1;
            Add(ind[1], a!.coeffs[x]);
            Add(ind[2], np);
          fi;
        fi;
      od;
      if ( -Length(a!.parts[x]) mod e)=r then
        np:=StructuralCopy(a!.parts[x]); Add(np, 1);
        Add(ind[1],a!.coeffs[x]);
        Add(ind[2],np);
      fi;
    od;
    if ind=[ [],[] ] then return Module(H,"S",0,[]);
    else return Collect(H,"S", ind[1], ind[2]);
    fi;
  end
); # RInducedModule

## String-induction: add s r's from each partition in x (ignoring
## multiplicities). Does both standard and q-induction.

## We look at the size of x.module to decide whether we want to use
## ordinary indcution or q-induction (in the Fock space). We could
## write H.operations.X.SInduce to as to make this choice for us, or
## do q-induction always, setting v=1 afterwards, but this seems the
## better choice.
InstallMethod(SInducedModuleOp,"string-induction on specht modules",
  [IsAlgebraObj,IsHeckeSpecht,IsInt,IsInt,IsInt],
  function(H, x, e, s, r) local coeffs, parts, y, z, sinduced;
    # add n nodes of residue r to the partition y from the i-th row down
    sinduced:=function(y, n, e, r, i) local ny, j, z;
      ny:=[];
      for j in [i..Length(y)-n+1] do
        if r=(y[j] - j + 1) mod e then
          if j=1 or y[j] < y[j-1] then
            z:=StructuralCopy(y);
            z[j]:=z[j] + 1; # only one node of residue r can be added
            if n=1 then Add(ny, z);   # no more nodes to add
            else Append(ny, sinduced(z, n-1, e, r, j+1));
            fi;
          fi;
        fi;
      od;
      return ny;
    end;

    if s=0 then return Module(x!.H,x!.module,1,[]); fi;
    coeffs:=[]; parts:=[];
    for y in [1..Length(x!.parts)] do
      Append(parts,sinduced(x!.parts[y], s, e, r, 1));
      Append(coeffs,List([1..Length(parts)-Length(coeffs)],r->x!.coeffs[y]));
      if r=( -Length(x!.parts[y]) mod e) then # add a node to the bottom
        z:=StructuralCopy(x!.parts[y]);
        Add(z,1);
        if s > 1 then                        # need to add some more nodes
          Append(parts,sinduced(z, s-1, e, r, 1));
          Append(coeffs,List([1..Length(parts)-Length(coeffs)],
                             r->x!.coeffs[y]));
        else Add(coeffs, x!.coeffs[y]); Add(parts, z);
        fi;
      fi;
    od;

    if coeffs=[] then return Module(H, x!.module,0,[]);
    else return Collect(H, x!.module, coeffs, parts);
    fi;
  end
);  # SInducedModule

## r-restriction
InstallMethod(RRestrictedModuleOp,"r-restriction on specht modules",
  [IsAlgebraObj,IsHeckeSpecht,IsInt,IsInt],
  function(H, a, e, r) local ind, x, i, j, np;
    ind:=[[],[]];
    for x in [1..Length(a!.parts)] do
      for i in [1..Length(a!.parts[x])] do
        if (a!.parts[x][i] - i) mod e=r then
          np:=StructuralCopy(a!.parts[x]);
          if i=Length(a!.parts[x]) or np[i] > np[i+1] then
            np[i]:=np[i] - 1;
            if np[i]=0 then Unbind(np[i]); fi;
            Add(ind[1], a!.coeffs[x]); Add(ind[2], np);
          fi;
        fi;
      od;
    od;
    if ind=[ [],[] ] then return Module(H,"S",0,[]);
    else return Collect(H,"S", ind[1], ind[2]);
    fi;
  end
); #RRestrictedModule

## string-restriction: remove m r's from each partition in x
InstallMethod(SRestrictedModuleOp,"string-restriction on specht modules",
  [IsAlgebraObj,IsHeckeSpecht,IsInt,IsInt,IsInt],
  function(H,x,e,s,r) local coeffs, parts, y, i, srestricted;
    ## remove n nodes from y from the ith row down
    srestricted:=function(y, n, e, r, i) local ny, j, z;
      ny:=[];
      for j in [i..Length(y)-n+1] do
        if r=(y[j] - j) mod e then
          if j=Length(y) or y[j] > y[j+1] then
            z:=StructuralCopy(y);
            z[j]:=z[j] - 1;
            if z[j]=0 then   # n must be 1
              Unbind(z[j]);
              Add(ny, z);
            elif n=1 then Add(ny, z); # no mode nodes to remove
            else Append(ny, srestricted(z, n-1, e, r, j+1));
            fi;
          fi;
        fi;
      od;
      return ny;
    end;

    coeffs:=[]; parts:=[];
    for y in [1..Length(x!.parts)] do
      Append(parts, srestricted(x!.parts[y], s, e, r, 1));
      Append(coeffs,List([1..Length(parts)-Length(coeffs)],i->x!.coeffs[y]));
    od;
    if parts=[] then return Module(H,"S",0,[]);
    else return Collect(H,"S", coeffs, parts);
    fi;
  end
);  # SRestrictedModule

## Induction and restriction; for S()
InstallMethod(RInducedModuleOp, "r-induction for specht modules",
  [IsAlgebraObj, IsHeckeSpecht, IsList],
  function(H, x, list) local r;
    if x=fail or x=0*x then return x;
    elif list=[] then return RInducedModule(H,x,1,0);
    elif H!.e=0 then
      Error("Induce, r-induction is not defined when e=0.");
    elif ForAny(list,r-> r>=H!.e or r<0) then
      Error("Induce, r-induction is defined only when 0<=r<e.\n");
    else
      for r in list do
        x:=RInducedModule(H,x,H!.e,r);
      od;
      return x;
    fi;
  end
);

InstallMethod(RInducedModuleOp, "r-induction for projective indecomposable modules",
  [IsAlgebraObj, IsHeckePIM, IsList],
  function(H, x, list) local nx;
    x:=RInducedModule(H,MakeSpechtOp(x,false),list);
    if x=fail or x=0*x then return x; fi;
    nx:=MakePIMOp(x,false);
    if nx<>fail then return nx; else return x; fi;
  end
);

InstallMethod(RInducedModuleOp, "r-induction for simple modules",
  [IsAlgebraObj, IsHeckeSimple, IsList],
  function(H, x, list) local nx;
    x:=RInducedModule(H,MakeSpechtOp(x,false),list);
    if x=fail or x=0*x then return x; fi;
    nx:=MakeSimpleOp(x,false);
    if nx<>fail then return nx; else return x; fi;
  end
);

InstallMethod(RInducedModuleOp, "r-induction for Fock space specht modules",
  [IsAlgebraObj,IsFockSpecht,IsList],
  function(H, x, list) local r;
    if list=[] then return Sum([0..H!.e-1],r->qSInducedModule(H,x,1,r));
    elif H!.e=0 then
      Error("Induce, r-induction is not defined when e=0.");
    elif ForAny(list,r-> r>=H!.e or r<0) then
      Error("Induce, r-induction is defined only when 0<=r<e.\n");
    else
      for r in list do   ## we could do slightly better here
        x:= qSInducedModule(H,x,1,r);
      od;
      return x;
    fi;
  end
);

InstallMethod(RInducedModuleOp,
  "r-induction for Fock space projective indecomposable modules",
  [IsAlgebraObj,IsFockPIM,IsList],
  function(H,x,list)
    return MakePIMOp(RInducedModule(H,MakeSpechtOp(x,false),list),false);
  end
);

InstallMethod(RInducedModuleOp,
  "r-induction for Fock space simple modules",
  [IsAlgebraObj,IsFockSimple,IsList],
  function(H,x,list)
    return MakeSimpleOp(RInducedModule(H,MakeSpechtOp(x,false),list),false);
  end
); # RInducedModule

InstallMethod(RRestrictedModuleOp, "r-restriction for specht modules",
  [IsAlgebraObj, IsHeckeSpecht, IsList],
  function(H, x, list) local r;
    if x=fail or x=0*x then return x;
    elif list=[] then return RRestrictedModule(H,x,1,0);
    elif H!.e=0 then
      Error("Restrict, r-restriction is not defined when e=0.");
   elif ForAny(list,r-> r>=H!.e or r<0) then
      Error("Restrict, r-restriction is defined only when 0<=r<e.\n");
    else
      for r in list do
        x:=RRestrictedModule(H,x,H!.e,r);
      od;
      return x;
    fi;
  end
);

InstallMethod(RRestrictedModuleOp, "r-restriction for projective indecomposable modules",
  [IsAlgebraObj, IsHeckePIM, IsList],
  function(H, x, list) local nx;
    x:=RRestrictedModule(H,MakeSpechtOp(x,false),list);
    if x=fail or x=0*x then return x; fi;
    nx:=MakePIMOp(x,false);
    if nx<>fail then return nx; else return x; fi;
  end
);

InstallMethod(RRestrictedModuleOp, "r-restriction for simple modules",
  [IsAlgebraObj, IsHeckeSimple, IsList],
  function(H, x, list) local nx;
    x:=RRestrictedModule(H,MakeSpechtOp(x,false),list);
    if x=fail or x=0*x then return x; fi;
    nx:=MakeSimpleOp(x,false);
    if nx<>fail then return nx; else return x; fi;
  end
);

InstallMethod(RRestrictedModuleOp, "r-restriction for Fock space specht modules",
  [IsAlgebraObj,IsFockSpecht,IsList],
  function(H, x, list) local r;
    if list=[] then return Sum([0..H!.e-1],r->qSRestrictedModule(H,x,1,r));
    elif H!.e=0 then
      Error("Restrict, r-restriction is not defined when e=0.");
    elif ForAny(list,r-> r>=H!.e or r<0) then
      Error("Restrict, r-restriction is defined only when 0<=r<e.\n");
    else
      for r in list do   ## we could do slightly better here
        x:= qSRestrictedModule(H,x,1,r);
      od;
      return x;
    fi;
  end
);

InstallMethod(RRestrictedModuleOp,
  "r-restriction for Fock space projective indecomposable modules",
  [IsAlgebraObj,IsFockPIM,IsList],
  function(H,x,list)
    return MakePIMOp(RRestrictedModule(H,MakeSpechtOp(x,false),list),false);
  end
);

InstallMethod(RRestrictedModuleOp,
  "r-restriction for Fock space simple modules",
  [IsAlgebraObj,IsFockSimple,IsList],
  function(H,x,list)
    return MakeSimpleOp(RRestrictedModule(H,MakeSpechtOp(x,false),list),false);
  end
); # RRestrictedModule

InstallMethod(SInducedModuleOp,"string induction for specht modules",
  [IsAlgebraObj, IsHeckeSpecht, IsList],
  function(H, x, list) local r;
    if x=fail or x=0*x then return x;
    elif Length(list)=1 then
      list:=list[1];
      if list=0 then return Module(H,"Sq",1,[]); fi;
      while list > 0 do
        x:=SInducedModule(H,x,1,1,0);
        list:=list-1;
      od;
      return x;
    elif H!.e=0 then
      Error("SInduce, string induction is not defined when e=0.");
    elif list[2]>H!.e or list[2]<0 then
      Error("SInduce, string induction is defined only when 0<=r<e.\n");
    else return SInducedModule(H, x, H!.e, list[1], list[2]);
    fi;
  end
);

InstallMethod(SInducedModuleOp, "string induction for projective indecomposable modules",
  [IsAlgebraObj, IsHeckePIM, IsList],
  function(H, x, list) local nx;
    x:=SInducedModule(H,MakeSpechtOp(x,false),list);
    if x=fail or x=0*x then return x; fi;
    nx:=MakePIMOp(x,false);
    if nx<>fail then return nx; else return x; fi;
  end
);

InstallMethod(SInducedModuleOp, "string induction for simple modules",
  [IsAlgebraObj, IsHeckeSimple, IsList],
  function(H, x, list) local nx;
    x:=SInducedModule(H,MakeSpechtOp(x,false),list);
    if x=fail or x=0*x then return x; fi;
    nx:=MakeSimpleOp(x,false);
    if nx<>fail then return nx; else return x; fi;
  end
);

InstallMethod(SInducedModuleOp, "string induction for Fock space specht modules",
  [IsAlgebraObj,IsFockSpecht,IsList],
  function(H, x, list) local r;
    if Length(list)=1 then
      list:=list[1];
      if list=0 then return Module(H,"Sq",1,[]); fi;
      while list > 0 do
        x:=Sum([0..H!.e-1],r->qSInducedModule(H,x,1,r));
        list:=list-1;
      od;
      return x;
    elif H!.e=0 then
      Error("SInduce, string induction is not defined when e=0.");
    elif list[2]>H!.e or list[2]<0 then
      Error("SInduce, string induction is defined only when 0<=r<e.\n");
    else return qSInducedModule(H, x, list[1], list[2]);
    fi;
  end
);

InstallMethod(SInducedModuleOp,
  "string induction for Fock space projective indecomposable modules",
  [IsAlgebraObj,IsFockPIM,IsList],
  function(H,x,list)
    return MakePIMOp(SInducedModule(H,MakeSpechtOp(x,false),list),false);
  end
);

InstallMethod(SInducedModuleOp,
  "string induction for Fock space simple modules",
  [IsAlgebraObj,IsFockSimple,IsList],
  function(H,x,list)
    return MakeSimpleOp(SInducedModule(H,MakeSpechtOp(x,false),list),false);
  end
); # SInducedModule

InstallMethod(SRestrictedModuleOp,"string restriction for specht modules",
  [IsAlgebraObj, IsHeckeSpecht, IsList],
  function(H, x, list) local r;
    if x=fail or x=0*x then return x;
    elif Length(list)=1 then
      list:=list[1];
      if list=0 then return Module(H,"Sq",1,[]); fi;
      while list > 0 do
        x:=SRestrictedModule(H,x,1,1,0);
        list:=list-1;
      od;
      return x;
    elif H!.e=0 then
      Error("SRestrict, r-restriction is not defined when e=0.");
    elif list[2]>H!.e or list[2]<0 then
      Error("SRestrict, r-restriction is defined only when 0<=r<e.\n");
    else return SRestrictedModule(H, x, H!.e, list[1], list[2]);
    fi;
  end
);

InstallMethod(SRestrictedModuleOp, "string restriction for projective indecomposable modules",
  [IsAlgebraObj, IsHeckePIM, IsList],
  function(H, x, list) local nx;
    x:=SRestrictedModule(H,MakeSpechtOp(x,false),list);
    if x=fail or x=0*x then return x; fi;
    nx:=MakePIMOp(x,false);
    if nx<>fail then return nx; else return x; fi;
  end
);

InstallMethod(SRestrictedModuleOp, "string restriction for simple modules",
  [IsAlgebraObj, IsHeckeSimple, IsList],
  function(H, x, list) local nx;
    x:=SRestrictedModule(H,MakeSpechtOp(x,false),list);
    if x=fail or x=0*x then return x; fi;
    nx:=MakeSimpleOp(x,false);
    if nx<>fail then return nx; else return x; fi;
  end
);

InstallMethod(SRestrictedModuleOp, "string restriction for Fock space specht modules",
  [IsAlgebraObj,IsFockSpecht,IsList],
  function(H, x, list) local r;
    if Length(list)=1 then
      list:=list[1];
      if list=0 then return Module(H,"Sq",1,[]); fi;
      while list > 0 do
        x:=Sum([0..H!.e-1],r->qSRestrictedModule(H,x,1,r));
        list:=list-1;
      od;
      return x;
    elif H!.e=0 then
      Error("SRestrict, string restriction is not defined when e=0.");
    elif list[2]>H!.e or list[2]<0 then
      Error("SRestrict, string restriction is defined only when 0<=r<e.\n");
    else return qSRestrictedModule(H, x, list[1], list[2]);
    fi;
  end
);

InstallMethod(SRestrictedModuleOp,
  "string restriction for Fock space projective indecomposable modules",
  [IsAlgebraObj,IsFockPIM,IsList],
  function(H,x,list)
    return MakePIMOp(SRestrictedModule(H,MakeSpechtOp(x,false),list),false);
  end
);

InstallMethod(SRestrictedModuleOp,
  "string restriction for Fock space simple modules",
  [IsAlgebraObj,IsFockSimple,IsList],
  function(H,x,list)
    return MakeSimpleOp(SRestrictedModule(H,MakeSpechtOp(x,false),list),false);
  end
); #SRestrictedModule

InstallMethod(RInducedModuleOp,
  "toplevel r-induction",
  [IsAlgebraObjModule,IsList],
  function(x,list)
    return RInducedModuleOp(x!.H,x,list);
  end
); #RInducedModule

InstallMethod(RRestrictedModuleOp,
  "toplevel r-restriction",
  [IsAlgebraObjModule,IsList],
  function(x,list)
    return RRestrictedModuleOp(x!.H,x,list);
  end
); #RRestrictedModule

InstallMethod(SInducedModuleOp,
  "toplevel string induction",
  [IsAlgebraObjModule,IsList],
  function(x,list)
    return SInducedModuleOp(x!.H,x,list);
  end
); #SInducedModule

InstallMethod(SRestrictedModuleOp,
  "toplevel string restriction",
  [IsAlgebraObjModule,IsList],
  function(x,list)
    return SRestrictedModuleOp(x!.H,x,list);
  end
); #SRestrictedModule

## Q-INDUCTION AND Q-RESTRICTION ###############################################

## notice that we can't pull the trick to induce all residues at once
## that we used in InducedModule() etc. as we have to keep track of the
## number of addable and removable nodes of each residue. Rather than
## do this we just call this function as many times as necessary.
InstallMethod(qSInducedModule,"q-induction for modules",
  [IsAlgebraObj,IsAlgebraObjModule,IsInt,IsInt],
  function(H,x,s,r) local coeffs, parts, y, z, qsinduced, v;
    v:=H!.Indeterminate;

    # add n nodes of residue r to the partition y from the i-th row down
    # here exp is the exponent of the indertminate
    qsinduced:=function(y, n, r, i, exp) local ny, j, z;
     ny:=[];
     for j in [i..Length(y)-n+1] do
       if y[j]>0 and r=(y[j]-j) mod H!.e and (j=Length(y) or y[j]>y[j+1])
       then exp:=exp-n;               ## removeable node of residue r
       elif r=(y[j] - j + 1) mod H!.e then
         if j=1 or y[j] < y[j-1] then ## addable node of residue r
           z:=StructuralCopy(y);
           z[j]:=z[j] + 1; # only one node of residue r can be added
           if n=1 then Add(ny, [exp,z] );   # no more nodes to add
           else Append(ny, qsinduced(z, n-1, r, j+1, exp));
           fi;
           exp:=exp+n;
         fi;
       fi;
     od;
     return ny;
    end;

    coeffs:=[]; parts:=[];
    for y in [1..Length(x!.parts)] do
      if r=( -Length(x!.parts[y]) mod H!.e) then # add a node to the bottom
        z:=StructuralCopy(x!.parts[y]);
        Add(z,0);
        for z in qsinduced(z,s,r,1,0)  do
          if z[1]=0 then Add(coeffs, x!.coeffs[y]);
          else Add(coeffs, x!.coeffs[y]*v^z[1]);
          fi;
          if z[2][Length(z[2])]=0 then Unbind(z[2][Length(z[2])]); fi;
          Add(parts, z[2]);
        od;
      else
        for z in qsinduced(x!.parts[y],s,r,1,0) do
          if z[1]=0 then Add(coeffs, x!.coeffs[y]);
          else Add(coeffs, x!.coeffs[y]*v^z[1]);
          fi;
          Add(parts, z[2]);
        od;
      fi;
    od;

    if coeffs=[] then return Module(H,x!.module,0,[]);
    else return Collect(H,x!.module, coeffs, parts);
    fi;
  end
);  # qSInducedModule

## string-restriction: remove m r's from each partition in x
## ** should allow restricting s-times also
InstallMethod(qSRestrictedModule,"q-restriction for modules",
  [IsAlgebraObj,IsAlgebraObjModule,IsInt,IsInt],
  function(H, x, s, r) local coeffs, parts, z, y, i, e, exp, v, qrestricted;
    v:=H!.Indeterminate;

    qrestricted:=function(y, n, r, i, exp) local ny, j, z;
      ny:=[];
      for j in [i,i-1..n] do
        if y[j]>0 and r=(y[j]+1-j) mod H!.e and (j=1 or y[j]<y[j-1]) then
           exp:=exp-n;                 ## an addable node of residue r
        elif r=(y[j] - j) mod H!.e then   ## removeable node of residue r
          if j=Length(y) or y[j] > y[j+1] then
            z:=StructuralCopy(y);
            z[j]:=z[j]-1;
            if z[j]=0 then Unbind(z[j]); fi;
            if n=1 then Add(ny, [exp,z]); # no mode nodes to remove
            else Append(ny, qrestricted(z, n-1, r, j-1, exp));
            fi;
            exp:=exp+n;
          fi;
        fi;
      od;
      return ny;
    end;

    e:=x!.H!.e;
    coeffs:=[]; parts:=[];
    if s=0 then return Module(H,"Sq",1,[]); fi;
    for y in [1..Length(x!.parts)] do
      if -Length(x!.parts[y]) mod H!.e = r then exp:=-s; else exp:=0; fi;
      for z in qrestricted(x!.parts[y], s, r, Length(x!.parts[y]), exp) do
        if z[1]=0 then Add(coeffs, x!.coeffs[y]);
        else Add(coeffs, x!.coeffs[y]*v^z[1]);
        fi;
        Add(parts, z[2]);
      od;
    od;
    if parts=[] then return Module(H,"Sq",0,[]);
    else return Collect(H, "Sq", coeffs, parts);
    fi;
  end
);  # qSRestrict

## DECOMPOSITION MATRICES ######################################################

## The following directory is searched by ReadDecompositionMatrix()
## when it is looking for decomposition matrices. By default, it points
## to the current directory (if set, the current directory is not
## searched).
if not IsBound(SpechtDirectory) then SpechtDirectory:=Directory("."); fi;

## This variable is what is used in the decomposition matrices files saved
## by SaveDecompositionMatrix() (and also the variable which contains them
## when they are read back in).
A_Specht_Decomposition_Matrix:=fail;

## Finally, we can define the creation function for decomposition matrices
## (note that NewDM() does not add the partition labels to the decomp.
## matrix; this used to be done here but now happens in PrintDM() because
## crystallized matrices may never be printed and this operation is
## expensive).
## **NOTE: we assume when extracting entries from d that d.rows is
## ordered lexicographically. If this is not the case then addition
## will not work properly.
InstallOtherMethod(DecompositionMatrix,"creates a new decomposition matrix",
  [IsAlgebraObj,IsList,IsList,IsBool],
  function(H, rows, cols, decompmat) local d;
    d := rec(d:=[],      # matrix entries
       rows:=rows, # matrix rows
       cols:=cols, # matrix cols
       inverse:=[], dimensions:=[], ## inverse matrix and dimensions
       H:=H
    );

    if decompmat then
      return Objectify(DecompositionMatrixType,d);
    else
      return Objectify(CrystalDecompositionMatrixType,d);
    fi;
  end
); # DecompositonMatrix

InstallMethod(\=, "for decomposition matrices",
  [IsDecompositionMatrix,IsDecompositionMatrix],
  function(d1,d2)
    return d1!.d=d2!.d and d1!.cols = d2!.cols and d1!.rows = d2!.rows;
  end
); # Equal matrices

InstallMethod(CopyDecompositionMatrix, "for decomposition matrices",
  [IsDecompositionMatrix],
  function(d) local newd;
    newd:=DecompositionMatrix(
      d!.H, StructuralCopy(d!.rows), StructuralCopy(d!.cols),
      not IsCrystalDecompositionMatrix(d)
      );
    newd!.d:=StructuralCopy(d!.d);
    newd!.inverse:=StructuralCopy(d!.inverse);
    newd!.dimensions:=StructuralCopy(d!.dimensions);
    return newd;
  end
); # CopyDecompositionMatrix

## Used by SaveDecomposition matrix to update CrystalMatrices[]
InstallMethod(Store,"for crystal decomposition matrices",
  [IsCrystalDecompositionMatrix,IsInt],
  function(d,n) d!.H!.CrystalMatrices[n]:=d; end
);

## Used by SaveDecomposition matrix to update DecompositionMatrices[]
InstallMethod(Store,"for decomposition matrices",[IsDecompositionMatrix,IsInt],
  function(d,n) d!.H!.DecompositionMatrices[n]:=d; end
);

## This also needs to be done for crystal matrices (this wasn't
## put in above because normal decomposition matrices don't
## specialise.
InstallMethod(Specialized,"specialise CDM",
  [IsCrystalDecompositionMatrix,IsInt],
  function(d,a) local sd, c, p, coeffs, v;
    v:=d!.H!.Indeterminate;
    sd:=DecompositionMatrix(d!.H,d!.rows,d!.cols,true);
    for c in [1..Length(d!.cols)] do
      if IsBound(d!.d[c]) then
        sd!.d[c]:=rec();
        coeffs:=List(d!.d[c].coeffs*v^0,p->Value(p,a));
        p:=List(coeffs,p->p<>0);
        if true in p then
          sd!.d[c]:=rec(coeffs:=ListBlist(coeffs,p),
                        parts:=ListBlist(d!.d[c].parts,p) );
        else sd!.d[c]:=rec(coeffs:=[0], parts:=[ [] ] );
        fi;
      fi;
      if IsBound(d!.inverse[c]) then
        coeffs:=List(d!.inverse[c].coeffs*v^0,p->Value(p,a));
        p:=List(coeffs,p->p<>0);
        if true in p then
          sd!.inverse[c]:=rec(coeffs:=ListBlist(coeffs,p),
                          parts:=ListBlist(d!.inverse[c].parts,p) );
        else sd!.d[c]:=rec(coeffs:=[0], parts:=[ [] ] );
        fi;
      fi;
    od;
    return sd;
  end
);

InstallMethod(Specialized,"specialise CDM",
  [IsCrystalDecompositionMatrix], a->Specialized(a,1)
);

## Specialization taking Xq -> X
InstallMethod(Specialized,"specialise Fock module",
  [IsFockModule,IsInt],
  function(x,a) local coeffs, c;
    coeffs:=List(x!.coeffs*(x!.H!.Indeterminate)^0,c->Value(c,a));
    c:=List(coeffs,c->c<>0);
    if true in c then return Module(x!.H,x!.module{[1]},
                                ListBlist(coeffs,c),ListBlist(x!.parts,c));
    else return Module(x!.H,x!.module{[1]},0,[]);
    fi;
  end
);

InstallMethod(Specialized,"specialise Fock module",
  [IsFockModule], x->Specialized(x,1)
); # Specialized

## writes D(mu) as a linear combination of S(nu)'s if possible
InstallMethod(Invert, "for a decomposition matrix and a partition",
  [IsDecompositionMatrix,IsList],
  function(d,mu) local c, S, D, inv, smu, l, tmp;
    if IsCrystalDecompositionMatrix(d) then S:="Sq"; D:="Dq";
    else S:="S"; D:="D";
    fi;

    c:=Position(d!.cols,mu);
    if c=fail then return fail;
    elif IsBound(d!.inverse[c]) then
      return Module(d!.H,S,d!.inverse[c].coeffs,
                    List(d!.inverse[c].parts,l->d!.cols[l]));
    fi;

    inv:=Module(d!.H,S,1,mu);
    tmp:=MakeSimpleOp(d,inv);
    if tmp=fail then
      smu:=fail;
    else
      smu:=Module(d!.H,D,1,mu)-tmp;
    fi;

    while smu<>fail and smu<>0*smu do
      inv:=inv+Module(d!.H,S,smu!.coeffs[1],smu!.parts[1]);
      tmp:=MakeSimpleOp(d, Module(d!.H,S,-smu!.coeffs[1],smu!.parts[1]));
      if tmp=fail then
        smu:=fail;
      else
        smu:=smu+tmp;
      fi;
    od;
    if smu=fail then return fail; fi;

    d!.inverse[c]:=rec(coeffs:=inv!.coeffs,
                  parts:=List(inv!.parts,l->Position(d!.cols,l)));
    return inv;
  end
);

#P This function adds the column for Px to the decomposition matrix <d>.
## if <checking>=true then Px is checked against its image under the
## Mullineux map; if this image is not there then we also insert it
## into <d>.
InstallMethod(AddIndecomposable,"fill out entries of decomposition matrix",
  [IsDecompositionMatrix,IsAlgebraObjModule,IsBool],
  function(d,Px,checking) local mPx, r, c;
    c:=Position(d!.cols, Px!.parts[Length(Px!.parts)]);
    if checking then
      ## first look to see if <Px> already exists in <d>
      if IsBound(d!.d[c]) then ## Px already exists
        Print("# AddIndecomposable: overwriting old value of P(",
              TightStringList(Px!.parts[Length(Px!.parts)]),") in <d>\n");
        Unbind(d!.inverse);       # just in case these were bound
        Unbind(d!.dimensions);
      fi;
      ## now looks at the image of <Px> under Mullineux
      if (not IsSchur(Px!.H) and IsERegular(Px!.H!.e,Px!.parts[Length(Px!.parts)]))
      and Px!.parts[Length(Px!.parts)]<>ConjugatePartition(Px!.parts[1]) then
        mPx:=MullineuxMap(Px);
        if IsBound(d!.d[Position(d!.cols,mPx!.parts[Length(Px!.parts)])])
        and MakePIMSpechtOp(d,mPx!.parts[Length(Px!.parts)]) <> mPx then
          Print("# AddIndecomposable(<d>,<Px>), WARNING: P(",
                TightStringList(Px!.parts[Length(Px!.parts)]), ") and P(",
                TightStringList(mPx!.parts[Length(Px!.parts)]),
                ") in <d> are incompatible\n");
        else
          Print("# AddIndecomposable(<d>,<Px>): adding MullineuxMap(<Px>) ",
                "to <d>\n");
          AddIndecomposable(d,mPx,false);
        fi;
      fi;
    fi;      # end of check
    d!.d[c]:=rec(coeffs:=Px!.coeffs,
                parts:=List(Px!.parts,r->Position(d!.rows,r)) );
  end
);

#F This function checks to see whether Px is indecomposable using the
## decomposition matrix d. The basic idea is to loop through all of the
## (e-regular, unless IsSpecht=false) projectives Py in Px such that Px-Py
## has non-negative coefficients and to then apply the q-Schaper theorem
## and induce simples, together with a few other tricks to decide
## whether ir not Py slits off. If yes, then we move on; if we can't
## decide (or don't know the value of Py), we spit the dummy and return
## false, printing a "message from our sponsor" explaining why we failed
## if called from outside InducedModule(). If we can count for all
## projectives then we return true. Note that in this case the value
## of Px may have changed, but we update the original value of px=Px
## before leaving (whether we return false or true).
InstallMethod(IsNewIndecomposableOp, "checks whether the given module is indecomposable",
  [IsAlgebraObj, IsDecompositionMatrix,IsAlgebraObjModule,IsDecompositionMatrix,IsList],
  function(H,d,px,oldd,Mu)
    local Px,nu,regs,Py,y,z,a,b,n,mu,m,M,Message;

    if px=fail and px=0*px then return false; fi;

    if Mu=[fail] then Message:=Ignore;
    else Message:=Print;
    fi;

    Px:=px; Py:=true; # strip PIMs from the top of Px
    while Py<>fail and Px<>0*Px do
      Py:=MakePIMSpechtOp(d,Px!.parts[Length(Px!.parts)]);
      if Py<>fail then Px:=Px-Px!.coeffs[Length(Px!.parts)]*Py; fi;
    od;
    if Px=0*Px then
      Message("# This module is a sum of known indecomposables.\n");
      return false;
    fi;
    if IntegralCoefficients(Px / Px!.coeffs[Length(Px!.parts)]) then
      Px:=Px / Px!.coeffs[Length(Px!.parts)];
   fi;

    regs:=Obstructions(d,Px);

    if Mu<>[] then regs:=Filtered(regs,mu->mu<Mu); fi;
    regs:=Obstructions(d,Px);
    for y in regs do   ## loop through projectives that might split

      if not IsSchur(H) and MullineuxMap(H!.e,ConjugatePartition(Px!.parts[1]))
      <>Px!.parts[Length(Px!.parts)] then
        Py:=true;  ## strip any known indecomposables off the bottom of Px
        while Py<>fail and Px<>0*Px do
          Py:=MakePIMSpechtOp(d,ConjugatePartition(Px!.parts[1]));
          if Py<>fail and IsERegular(Py!.H!.e,Py!.parts[Length(Py!.parts)]) then
            Px:=Px-Px!.coeffs[1]*MullineuxMap(Py);
          else Py:=fail;
          fi;
        od;
      fi;

      m:=0;            ## lower and upper bounds on decomposition the number
      if Px=0*Px then M:=0;
      else M:=Coefficient(Px,y)/Px!.coeffs[Length(Px!.parts)];
      fi;
      if M<>0 then Py:=MakePIMSpechtOp(d,y); fi;
      if not ( m=M or Px!.parts[Length(Px!.parts)]>=y ) then
        if Py=fail then
          Message("# The multiplicity of S(", TightStringList(y),
            ") in <Px> is zero; however, S(", TightStringList(y),
            ") is not known\n");
          px!.coeffs:=Px!.coeffs; px!.parts:=Px!.parts; return false;
        else
          Px:=Px-Coefficient(Px,y)*Py;
          if not PositiveCoefficients(Px) then BUG("IsNewIndecomposable",1);fi;
          M:=0;
        fi;
      fi;

      if Px<>0*Px and IntegralCoefficients(Px/Px!.coeffs[Length(Px!.parts)]) then
        Px:=Px/Px!.coeffs[Length(Px!.parts)];
      fi;

      ## remember that Px.coeffs[Length(Px.parts)] could be greater than 1
      if m<>M and (Coefficient(Px,y) mod Px!.coeffs[Length(Px!.parts)])<>0 then
        if Py=fail then
          Message("# <Px> is not indecomposable, as at least ",
            Coefficient(Px,y) mod Px!.coeffs[Length(Px!.parts)], " copies of P(",
            TightStringList(y), ") split off.\n# However, P(",
            TightStringList(y), ") is not known\n");
          px!.coeffs:=Px!.coeffs; px!.parts:=Px!.parts; return false;
        else
          ## this is at least projective; perhaps more still come off though
          Px:=Px-(Coefficient(Px,y) mod Px!.coeffs[Length(Px!.parts)])*Py;
          if not PositiveCoefficients(Px) then BUG("IsNewIndecomposable",2);fi;
          if IntegralCoefficients(Px/Px!.coeffs[Length(Px!.parts)]) then
            Px:=Px/Px!.coeffs[Length(Px!.parts)];
          fi;
        fi;
      fi;

      ## At this point the coefficient of Sx in Px divides the coefficient of
      ## Sy in Px. If Py splits off then it does so in multiples of
      ## (Px:Sx)=Px.coeffs[Length(Px.parts)].
      if m<>M and (Py=fail
        or PositiveCoefficients(Px-Px!.coeffs[Length(Px!.parts)]*Py))
      then
        ## use the q-Schaper theorem to test whether Sy is contained in Px
        M:=Minimum(M,InnerProduct(Px/Px!.coeffs[Length(Px!.parts)],
                                  Schaper(H, y)));
        if M=0 then # NO!
          ## Px-(Px:Sy)Py is still projective so substract Py if it is
          ## known. If Py=false (ie. not known), then at least we know
          ## that Px is not indecomposable, even though we couldn't
          ## calculate Py.
          if Py=fail then
            Message("# The multiplicity of S(", TightStringList(y),
              ") in P(", TightStringList(Px!.parts[Length(Px!.parts)]),
             ") is zero;\n#  however, P(", TightStringList(y),
             ") is not known.\n");
            px!.coeffs:=Px!.coeffs; px!.parts:=Px!.parts; return false;
          else
            Px:=Px-Coefficient(Px,y)*Py;
            if not PositiveCoefficients(Px) then BUG("IsNewIndecomposable",3);
            fi;
          fi;
        elif Px!.coeffs[Length(Px!.parts)]<>Coefficient(Px,y) then
          ## We know that (Px:Sy)>=m>0, but perhaps some Py's still split off

          m:=1;
          if m=M then
            if Coefficient(Px,y)<>m*Px!.coeffs[Length(Px!.parts)] then
              if Py=fail then
                Message("# The multiplicity of S(", TightStringList(y),
                    ") in P(",TightStringList(Px!.parts[Length(Px!.parts)]),
                    ") is ", m, "however, P(",TightStringList(y),
                    ") is unknown.\n");
                px!.coeffs:=Px!.coeffs; px!.parts:=Px!.parts;
                return false;
              else
                Px:=Px-(Coefficient(Px,y)-Px!.coeffs[Length(Px!.parts)]*m)*Py;
                if not PositiveCoefficients(Px) then
                  BUG("IsNewIndecomposable",4);
                fi;
              fi;
            fi;
          fi;

          if m<>M then
            ## see if we can calculate this decomposition number (this uses
            ## row and column removal)
            a:=DecompositionNumber(Px!.H, y, Px!.parts[Length(Px!.parts)]);
            if a<>fail then
              if Px!.coeffs[Length(Px!.parts)]*a=Coefficient(Px,y) then m:=a; M:=a;
              elif Py<>fail then
                ## precisely this many Py's come off
                Px:=Px-(Coefficient(Px,y)-Px!.coeffs[Length(Px!.parts)]*a)*Py;
                m:=a; M:=a; # upper and lower bounds are equal
                if not PositiveCoefficients(Px) then
                  BUG("IsNewIndecomposable",5);
                fi;
              fi;
            fi;
          fi;

          if m<>M and Py=fail then ## nothing else we can do
            Message("# The multiplicity of S(", TightStringList(y),
                    ") in P(",TightStringList(Px!.parts[Length(Px!.parts)]),
                    ") is at least ", m, " and at most ", M,
                    ";\n# however, P(",TightStringList(y),") is unknown.\n");
            px!.coeffs:=Px!.coeffs; px!.parts:=Px!.parts;
            return false;
          fi;

          if m<>M then
            ## Maybe the Mullineux map can lower M...
            M:=Minimum(M,Coefficient(Px,
                 Py!.parts[Length(Py!.parts)]/Px!.coeffs[Length(Px!.parts)]));
            if Coefficient(Px,y)>M*Px!.coeffs[Length(Px!.parts)] then
              Px:=Px-(Coefficient(Px,y)-M*Px!.coeffs[Length(Px!.parts)])*Py;
            fi;
            while m<M and not
            PositiveCoefficients(Px-(Px!.coeffs[Length(Px!.parts)]*(M-m))*Py) do
              m:=m+1;
            od;
          fi;

          ## finally, we take a look at inducing the simples in oldd
          if m<>M and oldd<>fail then
            if not IsBound(oldd!.simples) then ## we have to first induce them
              oldd!.simples:=[];
              if IsBound(oldd!.ind) then       ## defined in InducedModule()
               for mu in [1..Length(oldd!.cols)] do
                  a:=Invert(oldd,oldd!.cols[mu]);
                  if a<>fail then
                    for n in [1..H!.e] do  ## induce simples of oldd
                      z:=Sum([1..Length(a!.coeffs)],b->a!.coeffs[b]
                           *oldd!.ind[Position(oldd!.rows,a!.parts[b])][n]);
                      if z<>0*z then Add(oldd!.simples,z);fi;
                    od;
                  fi;
                od;
              else   ## do everything by hand
                for mu in [1..Length(oldd!.cols)] do
                  a:=Invert(oldd,oldd!.cols[mu]);
                  if a<>fail then
                    for n in [0..H!.e-1] do
                      z:=RInducedModule(H,a,H!.e,n);
                      if z<>0*z then Add(oldd!.simples,z); fi;
                    od;
                  fi;
                od;
              fi;
            fi;
            mu:=Length(oldd!.simples);
            while mu >0 and m<M do
              z:=oldd!.simples[mu];
              if y=regs[Length(regs)]
              or Lexicographic(z!.parts[1],Py!.parts[Length(Py!.parts)]) then
                a:=InnerProduct(z,Py);
                if a<>0 then
                  b:=InnerProduct(z,Px)/Px!.coeffs[Length(Px!.parts)];
                  m:=Maximum(m,M-Int(b/a));
                fi;
              fi;
              mu:=mu-1;
            od;
            if Coefficient(Px,y)>M*Px!.coeffs[Length(Px!.parts)] then
              Px:=Px-(Coefficient(Px,y)-M*Px!.coeffs[Length(Px!.parts)])*Py;
            fi;
          fi;
          if m<>M then ## nothing else we can do
            px!.coeffs:=Px!.coeffs; px!.parts:=Px!.parts;
            Message("# The multiplicity of S(", TightStringList(y),
                ") in P(",TightStringList(Px!.parts[Length(Px!.parts)]),
                ") is at least ", m, " and at most ", M, ".\n");
            return false;
          fi;
        fi;   ## q-Schaper test
      fi;
    od;
    if Px=0*Px then
      Message("# This module is a sum of known indecomposables.\n");
      return false;
    elif Px!.coeffs[Length(Px!.parts)]<>1 then BUG("IsNewIndecomposable",6);
    else px!.coeffs:=Px!.coeffs; px!.parts:=Px!.parts; return true;
   fi;
  end
);  # IsNewIndecomposable

#P A front end to d.operations.AddIndecomposable. This function adds <Px>
## into the decomposition matrix <d> and checks that it is compatible with
## its image under the Mullineux map, if this is already in <d>, and
## inserts it if it is not.
InstallMethod(AddIndecomposable,"fill out entries of decomposition matrix",
  [IsDecompositionMatrix,IsHeckeSpecht],
  function(d, Px)
    if Position(d!.cols, Px!.parts[Length(Px!.parts)])=fail then
      Print("# The projective P(",TightStringList(Px!.parts[Length(Px!.parts)]),
            ") is not listed in <D>\n");
    else AddIndecomposable(d,Px,true);
    fi;
  end
);# AddIndecomposable

## This function will read the file "n" if n is a string; otherwise
## it will look for the relevant file for H(Sym_n). If crystal=true
## this will be a crystal decomposition matrix, otherwise it will be
## a 'normal' decomposition matrix. When IsInt(n) the lists
## CrystalMatrices[] and DecompositionMatrices[] are also checked, and
## the crystal decomposition matrix is calculated if it is not found
## and crystal=true (and IsInt(n)).
## Question: if crystal=false but IsBound(CrystalMatrices[n]) should
## we still try and read e<H.e>p0.n or specialize CrystalMatrices[n]
## immediately. We try and read the file first...
InstallMethod(ReadDecompositionMatrix, "load matrix from library",
  [IsAlgebraObj,IsInt,IsBool],
  function(H, n, crystal) local d, file, SpechtDirectory;

    if not IsBound(SpechtDirectory) then SpechtDirectory:=""; fi;

    if crystal then
      if IsBound(H!.CrystalMatrices[n]) then
        d:=H!.CrystalMatrices[n];
        if d=fail and H!.info.SpechtDirectory=SpechtDirectory then
          return fail;
        elif d<>fail and ForAll([1..Length(d!.cols)],c->IsBound(d!.d[c]))
        then return d;
        fi;
      fi;
      file:=Concatenation("e",String(H!.e),"crys.",String(n));
    else
      file:=Concatenation(H!.HeckeRing,".",String(n));
    fi;
    d:=ReadDecompositionMatrix(H,file,crystal);
    if crystal then H!.CrystalMatrices[n]:=d; fi;
    return d;
  end
);

InstallMethod(ReadDecompositionMatrix, "load matrix from library",
  [IsAlgebraObj,IsString,IsBool],
  function(H, str, crystal)
    local msg, file, M, d, c, parts, coeffs, p, x, r, cm, rm;

    A_Specht_Decomposition_Matrix:=fail; ## just in case

    file:=Filename([Directory("."),SpechtDirectory,H!.info.Library],str);
    if crystal then
      msg:="ReadCrystalMatrix-";
    else
      msg:="ReadDecompositionMatrix-";
    fi;

    d:=fail;

    if file <> fail then Read(file); fi;

    if A_Specht_Decomposition_Matrix<>fail then   ## extract matrix from M
      M:=A_Specht_Decomposition_Matrix;
      A_Specht_Decomposition_Matrix:=fail;
      r:=Set(M.rows); c:=Set(M.cols);
      if not IsSchur(H) and r=c then
        d:=DecompositionMatrix(H,r,
              Filtered(c,x->IsERegular(H!.e,x)),not IsBound(M.crystal));
      elif not IsSchur(H) then
        d:=DecompositionMatrix(H,r,c,not IsBound(M.crystal));
      else
        d:=DecompositionMatrix(H,r,r,not IsBound(M.crystal));
      fi;
      if IsSet(M.rows) and IsSet(M.cols) then ## new format
        if IsBound(M.matname) then d!.matname:=M.matname; fi;
        for c in [1..Length(d!.cols)] do
          cm:=Position(M.cols, d!.cols[c]);
          if cm<>fail and IsBound(M.d[cm]) then
            x:=M.d[cm];
            parts:=[]; coeffs:=[];
            for rm in [1..Length(x)/2] do
              r:=Position(d!.rows,M.rows[x[rm+Length(x)/2]]);
              if r<>fail then
                Add(parts,r);
                if IsInt(x[rm]) then Add(coeffs,x[rm]);
                else
                  p:=LaurentPolynomialByCoefficients(
                    FamilyObj(1),
                    x[rm]{[2..Length(x[rm])]},x[rm]);
                  Add(coeffs,p);
                fi;
              fi;
            od;
            if parts<>[] then   ## paranoia
              SortParallel(parts,coeffs);
              d!.d[c]:=rec(parts:=parts,coeffs:=coeffs);
           fi;
          fi;
        od;
      else  ## old format
        d!.d:=List(c, r->rec(coeffs:=[], parts:=[]));
        ## next, we unravel the decomposition matrix
        for rm in [1..Length(M.rows)] do
          r:=Position(d!.rows,M.rows[rm]);
          if r<>fail then
            x:=1;
            while x<Length(M.d[rm]) do
              c:=Position(d!.cols,M.cols[M.d[rm][x]]);
              if c<>fail then
                Add(d!.d[c].coeffs, M.d[rm][x+1]);
                Add(d!.d[c].parts, r);
              fi;
              x:=x+2;
            od;
          fi;
        od;
        for c in [1..Length(d!.d)] do
          if d!.d[c].parts=[] then Unbind(d!.d[c]);
          else SortParallel(d!.d[c].parts, d!.d[c].coeffs);
          fi;
        od;
      fi;
    fi;
    return d;
  end
); # ReadDecompositionMatrix

## Look up the decomposition matrix in the library files and in the
## internal lists and return it if it is known.
## NOTE: this function does not use the crystal basis to calculate the
## decomposition matrix because it is called by the various conversion
## functions X->Y which will only need a small part of the matrix in
## general. The function FindDecompositionMatrix() also uses the crystal
## basis.
InstallMethod(KnownDecompositionMatrix,"looks for a known decomposition matrix",
  [IsAlgebraObj,IsInt],
  function(H,n)
    local d, x, r, c;

    if IsBound(H!.DecompositionMatrices[n]) then
      d:=H!.DecompositionMatrices[n];
      if ( d<>fail and ForAll([1..Length(d!.cols)],c->IsBound(d!.d[c])) )
      or H!.info.SpechtDirectory=SpechtDirectory then return d;
      elif H!.info.SpechtDirectory<>SpechtDirectory then
        for x in [1..Length(H!.DecompositionMatrices)] do
          if IsBound(H!.DecompositionMatrices[x]) and
          H!.DecompositionMatrices[x]=fail then
            Unbind(H!.DecompositionMatrices[x]);
          fi;
        od;
      fi;
    fi;
    d:=ReadDecompositionMatrix(H,n,false);

    ## next we look for crystal matrices
    if d=fail and not IsSchur(H) and IsZeroCharacteristic(H) then
      d:=ReadDecompositionMatrix(H,n,true);
      if d<>fail then d:=Specialized(d); fi;
    fi;

    if d=fail and n<2*H!.e then
      ## decomposition matrix can be calculated
      r:=Partitions(n);
      if not IsSchur(H) then c:=ERegularPartitions(H!.e,n);
      else c:=r;
      fi;
      d:=DecompositionMatrix(H, r, c, true);

      for x in [1..Length(d!.cols)] do
        if IsECore(H,d!.cols[x]) then    ## an e-core
          AddIndecomposable(d,
            Module(H,"S",1,d!.cols[x]),false);
        elif IsSimpleModule(H,d!.cols[x]) then ## first e-weight 1 partition
          c:=Module(H,"S",1,ECore(H,d!.cols[x]) )
                   *HeckeOmega(H,"S",H!.e);
          for r in [2..Length(c!.parts)] do
            AddIndecomposable(d,
                  Module(H,"S",[1,1],c!.parts{[r-1,r]}),false);
          od;
          if IsSchur(H) then
            AddIndecomposable(d,
              Module(H,"S",1,c!.parts[1]),false);
          fi;
        fi;
      od;
    elif IsBound(H!.DecompositionMatrices[n]) then ## partial answer only
      return H!.DecompositionMatrices[n];
    fi;
    H!.DecompositionMatrices[n]:=d;
    return d;
  end
);  # KnownDecompositionMatrix

## almost identical to KnownDecompositionMatrix except that if this function
## fails then the crystalized decomposition matrix is calculated.
InstallMethod(FindDecompositionMatrix,"find or calculate CDM",
  [IsAlgebraObj,IsInt],
  function(H,n) local d,c;
    d:=KnownDecompositionMatrix(H,n);
    if d=fail and not IsSchur(H) and IsZeroCharacteristic(H) then
      d:=DecompositionMatrix(H,
              Partitions(n),ERegularPartitions(H!.e,n),false);
      for c in [1..Length(d!.cols)] do
        if not IsBound(d!.d[c]) then
          AddIndecomposable(d,FindPq(H,d!.cols[c]),false);
        fi;
      od;
      H!.CrystalMatrices[n]:=d;
      d:=Specialized(d);
      H!.DecompositionMatrices[n]:=d;
    fi;
    return d;
  end
); # FindDecompositionMatrix

## Crystal basis elements ######################################################

#########################################################################
## Next, for fields of characteristic 0, we implement the LLT algorithm.
## Whenever a crystal basis element of the Fock space is calculated we
## store it in the relevant decomposition matrix n CrystalMatrices[].
## The actual LLT algorithm is contained in the function Pq (should
## really be called Pv...), but there are also functions Sq and Dq as
## well. These functions work as follows:
##   FindPq(mu) -> sum_nu d_{nu,mu} S(nu)
##   FindSq(mu) -> sum_nu d_{mu,nu} D(nu)
##   FindDq(mu) -> sum_nu d_{mu,nu}^{-1} S(nu)
## The later two functions will call Pq() as needed. The "modules" x
## returned by these functions have x.module equal to "Pq", "Sq" and
## "Dq" respectively to distinguish them from the specialized versions.
## Accordingly we need H.operations.Xq for X = S, P, and D; these are
## defined after Pq(), Sq(), and Dq() (which they make use of).

## Retrieves or calculates the crystal basis element Pq(mu)
InstallMethod(FindPq,"finds the crystal basis element Pq(mu)",
  [IsAlgebraObj,IsList],
  function(H,mu) local  n, c, CDM, i, r, s, x, v, val,coeffs,tmp;
    v:=H!.Indeterminate;

    if mu=[] then return Module(H,"Sq",v^0,[]); fi;
    n:=Sum(mu);

    ## first we see if we have already calculated Pq(mu)
    if not IsBound(H!.CrystalMatrices[n]) or H!.CrystalMatrices[n]=fail then
      x:=ReadDecompositionMatrix(H,n,true);
      if x=fail then
        H!.CrystalMatrices[n]:=DecompositionMatrix(H,
          Partitions(n), ERegularPartitions(H!.e,n), false);
      fi;
    fi;
    CDM:=H!.CrystalMatrices[n];
    c:=Position(CDM!.cols,mu);
    if IsBound(CDM!.d[c]) then
      return Module(H,"Sq",CDM!.d[c].coeffs,
                 List(CDM!.d[c].parts, s->CDM!.rows[s]) );
    fi;

    if IsECore(H!.e,mu) then
      x:=Module(H,"Sq",v^0,mu);
    elif EWeight(H!.e,mu)=1 then
      x:=Module(H,"Sq",v^0,ECore(H!.e,mu))
                 * HeckeOmega(H,"Sq",H!.e);
      r:=Position(x!.parts,mu);
      if r=1 then
        x!.parts:=x!.parts{[1]};
        x!.coeffs:=[v^0];
      else
        x!.parts:=x!.parts{[r-1,r]};
        x!.coeffs:=[v,v^0];
      fi;
    else  ## we calculate Pq(mu) recursively using LLT

      ## don't want to change the original mu
      mu:=StructuralCopy(mu);
      i:=1;
      while i<Length(mu) and mu[i]=mu[i+1] do
        i:=i+1;
      od;
      r:=(mu[i]-i) mod H!.e;
      mu[i]:=mu[i]-1;
      s:=1;
      i:=i+1;
      while i<=Length(mu) do
        while i<>Length(mu) and mu[i]=mu[i+1] do
          i:=i+1;
        od;
        if r=(mu[i]-i) mod H!.e then
          s:=s+1;
          mu[i]:=mu[i]-1;
          i:=i+1;
        else i:=Length(mu)+1;
        fi;
      od;
      if mu[Length(mu)]=0  then Unbind( mu[Length(mu)] ); fi;
      x:=qSInducedModule(H, FindPq(H,mu), s, r);
      n:=1;
      while n<Length(x!.parts) do
        tmp:=CoefficientsOfLaurentPolynomial(x!.coeffs[Length(x!.parts)-n]);
        if tmp[2]>0 then n:=n+1;
        else
          r := StructuralCopy( x!.coeffs[Length(x!.parts)-n] );
          tmp:=ShallowCopy(CoefficientsOfLaurentPolynomial(r));
          mu:=x!.parts[Length(x!.parts)-n];
          if Length(tmp[1]) < 1-tmp[1] then
            tmp[1]:=Concatenation(tmp[1],
              List([1..Length(tmp[1])-1-tmp[2]], i->0));
          fi;
          tmp[1]:=tmp[1]{[1..1-tmp[2]]};
          tmp[1]:=Concatenation(tmp[1], Reversed(tmp[1]{[1..-tmp[2]]}));
          r:=LaurentPolynomialByCoefficients(FamilyObj(1),tmp[1],tmp[2]);
          x := x-r*FindPq(H,mu);
          if mu in x!.parts then n:= n+1; fi;
        fi;
      od;
      r := List(x!.coeffs, s->s <> 0 * v ^ 0);
      if false in r then
        x!.coeffs := ListBlist( x!.coeffs, r );
        x!.parts := ListBlist( x!.parts, r );
      fi;
    fi;

    ## having found x we add it to CDM
    CDM!.d[c]:=rec(coeffs:=x!.coeffs,
                  parts:=List(x!.parts, r->Position(CDM!.rows,r)) );

    ## for good measure we also add the Mullineux image of Pq(mu) to CDM
    ## (see LLT Theorem 7.2)
    n:=Position(CDM!.cols,ConjugatePartition(x!.parts[1]));
    if c<>n then             ## not self-image under MullineuxMap
      r:=List(x!.coeffs*v^0, i->Value(i,v^-1));     ## v -> v^-1
      for i in [Length(r),Length(r)-1..1] do   ## multiply by r[1]
        tmp:=ShallowCopy(CoefficientsOfLaurentPolynomial(r[i]));
        tmp[2]:=tmp[2]-CoefficientsOfLaurentPolynomial(r[1])[2];
        r[i]:=LaurentPolynomialByCoefficients(FamilyObj(1),tmp[1],tmp[2]);
      od;
      s:=List(x!.parts, mu->Position(CDM!.rows,ConjugatePartition(mu)));
      SortParallel(s,r);
      CDM!.d[n]:=rec(coeffs:=r,parts:=s);
    fi;
    return x;
  end
); # FindPq

## Strictly speaking the functions Sq() and Dq() are superfluous as
## these functions could be carried out by the decomposition matrix
## operations; however the point is that the crystal matrices are
## allowed to have missing columns, and if any needed columns missing
## these functions calculate them on the fly via using Pq().

## writes S(mu), from the Fock space, as a sum of D(nu)
InstallMethod(FindSq,"write Sq(mu) as sum of Dq(mu)",
  [IsAlgebraObj,IsList],
  function(H,mu) local x, r, CDM, n;
    n:=Sum(mu);

    ## we need the list of e-regular partitions. as we will have to
    ## create CrystalMatrices[n] anyway, we get the columns from there.
    if not IsBound(H!.CrystalMatrices[n]) or H!.CrystalMatrices[n]=fail then
      r:=Partitions(n);
      H!.CrystalMatrices[n]:=DecompositionMatrix(H,
        r, ERegularPartitions(H!.e,n), false);
    fi;
    CDM:=H!.CrystalMatrices[n];

    x:=Module(H,"Dq",0,[]);
    r:=Length(CDM!.cols);
    mu:=Position(CDM!.rows,mu);
    while r>0 and CDM!.cols[r]>=CDM!.rows[mu] do
      if not IsBound(CDM!.d[r]) then FindPq(H,CDM!.cols[r]); fi;
      n:=Position(CDM!.d[r].parts,mu);
      if n<>fail then
        x:=x+Module(H,"Dq",CDM!.d[r].coeffs[n],CDM!.cols[r]);
      fi;
      r:=r-1;
    od;
    return x;
  end
);

## write D(mu), from the Fock space, as a sum of S(nu)
InstallMethod(FindDq, "write Dq(mu) as sum of Sq(mu)",
  [IsAlgebraObj,IsList],
  function(H,mu) local inv, x, c, CDM;
    inv:=Module(H,"Sq",1,mu);
    x:=Module(H,"Dq",1,mu)-FindSq(H,mu);
    CDM:=H!.CrystalMatrices[Sum(mu)];
    c:=Position(CDM!.cols,mu);

    if IsBound(CDM!.inverse[c]) then
      return Module(H,"Sq",CDM!.inverse[c].coeffs,
                                List(CDM!.inverse[c].parts,c->CDM!.cols[c]));
    fi;

    while x<>0*x do
      c:=Length(x!.parts);
      inv:=inv+Module(H,"Sq",x!.coeffs[c],x!.parts[c]);
      x:=x-x!.coeffs[c]*FindSq(H,x!.parts[c]);
    od;

    ## now place answer back in CDM
    CDM!.inverse[Position(CDM!.cols,mu)]:=rec(coeffs:=inv!.coeffs,
             parts:=List(inv!.parts,c->Position(CDM!.cols,c)) );
    return inv;
  end
); # FindDq

