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

Class(PrunedPRDFT, TaggedNonTerminal, rec(
    abbrevs := [
        (n,blk,pat) -> Checked(IsPosIntSym(n), IsPosIntSym(blk), IsList(pat),
            AnySyms(n,blk) or (IsInt(_unwrap(n)/_unwrap(blk)) and ForAll(pat, i->IsInt(i) and i < n/blk)),
            [_unwrap(n), 1, _unwrap(blk), pat]),
        (n,k,blk,pat) -> Checked(IsPosIntSym(n), IsIntSym(k), IsPosIntSym(blk), IsList(pat),
            AnySyms(n,k) or Gcd(_unwrap(n),_unwrap(k))=1,
            AnySyms(n,blk) or (IsInt(_unwrap(n)/_unwrap(blk)) and ForAll(pat, i->IsInt(i) and i < n/blk)),
            [_unwrap(n), When(AnySyms(n,k), k, k mod _unwrap(n)), _unwrap(blk), pat])
        ],

    dims := self >> let(
        size := let(n:=self.params[1], 2*(Int(n/2)+1)),
        d := [size, self.params[3]*Length(self.params[4])],
        When(self.transposed, [d[2], d[1]], d)
    ),

    terminate := self >> let(
        size := self.params[1], blk := self.params[3], pat := self.params[4],
        res := Compose(PRDFT(size, self.params[2]),
               Tensor(Mat(List(pat, i->BasisVec(size/blk, i))).transpose(), I(blk))).terminate(),
        When(self.transposed, res.transpose(), res)
    ),

    isReal    := True,
    normalizedArithCost := self >> let(n := self.params[1], IntDouble(2.5 * n * d_log(n) / d_log(2))),
    TType := TReal
));

_pruned_PRF12_CT_Children := (N,k,PRFt,DFTt,PRFtp,PRF1) -> Map2(DivisorPairs(N),
    (m,n) -> When(IsEvenInt(n),
	[ PRFt(m,k), DFTt(m,k), PRFtp(m,k) ] :: PRF1(m,n,k),
	[ PRFt(m,k), DFTt(m,k) ] :: PRF1(m,n,k))
);

_pruned_PRF12_CT_Stage1 := (N,k,C,Conj,Tw) ->
    let(m:=Cols(C[1]),
        n:=N/m,
        Nf:=Int(N/2),
        nf:=Int(n/2),
        nc:=Int((n+1)/2),
        mf:=Int(m/2),
        mc:=Int((m+1)/2),
        j:=Ind(nc-1),
        SUM(
            RC(Scat(H(Nf+1,mf+1,0,n))) * C[1] * Gath(H(2*(nf+1)*m, m, 0, 2*(nf+1))),
            When(nc=1, [],
                ISum(j,
                    RC(Scat(BH(Nf+1,N,m,j+1,n))) *
                    Conj * RC(C[2]) * Tw(j) *
                    RC(Gath(H((nf+1)*m, m, j+1, nf+1)))
                )
            ),
            When(IsOddInt(n), [],
                RC(Scat(H(Nf+1,mc,nf,n))) * C[3] * Gath(H(2*(nf+1)*m, m, 2*nf, 2*(nf+1)))
            )
        )
    );

_pruned_IPRF12_CT_Stage2 := (N,k,C,Conj,Tw) ->
    let(m:=Rows(C[1]),
        n:=N/m,
        Nf:=Int(N/2),
        nf:=Int(n/2),
        nc:=Int((n+1)/2),
        mf:=Int(m/2),
        mc:=Int((m+1)/2),
        j:=Ind(nc-1),
        SUM(
            Scat(H(2*(nf+1)*m, m, 0, 2*(nf+1))) * C[1] * RC(Gath(H(Nf+1,mf+1,0,n))),
        	When(nc=1, [],
                ISum(j,
                    RC(Scat(H((nf+1)*m, m, j+1, nf+1))) *
                    Tw(j) * RC(C[2]) * Conj *
    	            RC(Gath(BH(Nf+1,N,m,j+1,n)))
                )
            ),
        	When(IsOddInt(n), [],
    	       Scat(H(2*(nf+1)*m, m, 2*nf, 2*(nf+1))) * C[3] * RC(Gath(H(Nf+1,mc,nf,n))))
        )
    );

NewRulesFor(PrunedPRDFT, rec(
    PrunedPRDFT_base := rec(
       forTransposition := false,
       maxSize := 256,
       applicable := (self, nt) >> not nt.hasTags() and nt.params[1] <= self.maxSize and nt.params[3] = 1,
       children := nt -> [[PRDFT(nt.params[1], nt.params[2])]],
       apply := (nt, C, cnt) -> let(size := nt.params[1], blk := nt.params[3], pat := nt.params[4],
            C[1] * Tensor(Mat(List(pat, i->BasisVec(size/blk, i))).transpose(), I(blk)).terminate())
    ),
    PrunedPRDFT_DFT := rec(
       forTransposition := false,
       applicable := (self, nt) >> nt.params[1] = nt.params[3] and nt.params[4] = [0],
       children := nt -> [[ PRDFT(nt.params[1], nt.params[2]).withTags(nt.getTags()) ]],
       apply := (nt, C, cnt) -> C[1]
    ),
    PrunedPRDFT_CT_rec_block := rec(
       forTransposition := true,
       fPrecompute := f->fPrecompute(f),
       applicable := (self, nt) >> nt.params[1] > 2
            and not nt.hasTags()
            and not IsPrime(nt.params[1])
            and nt.params[3] = 1
            and Last(nt.params[4])+1-nt.params[4][1] = Length(Set(nt.params[4]))
            and ForAny(Map2(DivisorPairs(nt.params[1]), (m,n)->_ctpr_applicable(m, n, nt.params[4])), i->i),
       children := (nt) -> Filtered(_pruned_PRF12_CT_Children(nt.params[1],nt.params[2],PRDFT1, DFT1, PRDFT3,
                                        (m, n, k)->_pruned_children(m, n, nt.params[4], (r, sp) -> PrunedPRDFT(r, k mod n, nt.params[3], sp))),
                              lst -> ForAll(Filtered(lst, i->ObjId(i) = PrunedPRDFT), j->j.params[4] <> [])),
       apply := (self, nt, C, cnt) >> let(N:=nt.params[1], k:=nt.params[2], m:=Cols(C[1]), n := N/m,
            _pruned_PRF12_CT_Stage1(N, k, C, Diag(BHD(m,1,-1)), j->RC(Diag(self.fPrecompute(Twid(N,m,k,0,0,j+1))))) *
            _build_stack(m, n, nt.params[4], let(s1len := When(IsEvenInt(n), 3, 2), C{[s1len+1..Length(C)]}), nt.dims()[2], Gath, IterVStack, VStack, (a,b)->a*b))
    )
));

Class(PrunedIPRDFT, TaggedNonTerminal, rec(
    abbrevs := [
        (n,blk,pat) -> Checked(IsPosIntSym(n), IsPosIntSym(blk), IsList(pat),
            AnySyms(n,blk) or (IsInt(_unwrap(n)/_unwrap(blk)) and ForAll(pat, i->IsInt(i) and i < n/blk)),
            [_unwrap(n), 1, _unwrap(blk), pat]),
        (n,k,blk,pat) -> Checked(IsPosIntSym(n), IsIntSym(k), IsPosIntSym(blk), IsList(pat),
            AnySyms(n,k) or Gcd(_unwrap(n),_unwrap(k))=1,
            AnySyms(n,blk) or (IsInt(_unwrap(n)/_unwrap(blk)) and ForAll(pat, i->IsInt(i) and i < n/blk)),
            [_unwrap(n), When(AnySyms(n,k), k, k mod _unwrap(n)), _unwrap(blk), pat])
        ],

    dims := self >> let(
        size := let(n:=self.params[1], 2*(Int(n/2)+1)),
        d := [self.params[3]*Length(self.params[4]), size],
        When(self.transposed, [d[2], d[1]], d)
    ),

    terminate := self >> let(
        size := self.params[1], blk := self.params[3], pat := self.params[4],
        res := Compose(Tensor(Mat(List(pat, i->BasisVec(size/blk, i))), I(blk)),
                       IPRDFT(size, self.params[2])).terminate(),
        When(self.transposed, res.transpose(), res)
    ),

    isReal    := True,
    normalizedArithCost := self >> let(n := self.params[1], IntDouble(2.5 * n * d_log(n) / d_log(2))),
    TType := TReal
));

NewRulesFor(PrunedIPRDFT, rec(
    PrunedIPRDFT_base := rec(
       forTransposition := false,
       maxSize := 256,
       applicable := (self, nt) >> not nt.hasTags() and nt.params[1] <= self.maxSize and nt.params[3] = 1,
       children := nt -> [[IPRDFT(nt.params[1], nt.params[2])]],
       apply := (nt, C, cnt) -> let(size := nt.params[1], blk := nt.params[3], pat := nt.params[4],
            Tensor(Mat(List(pat, i->BasisVec(size/blk, i))), I(blk)).terminate() * C[1] )
    ),
    PrunedIPRDFT_DFT := rec(
       forTransposition := false,
       applicable := (self, nt) >> nt.params[1] = nt.params[3] and nt.params[4] = [0],
       children := nt -> [[ IPRDFT(nt.params[1], nt.params[2]).withTags(nt.getTags()) ]],
       apply := (nt, C, cnt) -> C[1]
    ),
    PrunedIPRDFT_CT_rec_block := rec(
       forTransposition := true,
       applicable := (self, nt) >> nt.params[1] > 2
            and not nt.hasTags()
            and not IsPrime(nt.params[1])
            and nt.params[3] = 1
            and Last(nt.params[4])+1-nt.params[4][1] = Length(Set(nt.params[4]))
            and ForAny(Map2(DivisorPairs(nt.params[1]), (m,n)->_ctpr_applicable(m, n, nt.params[4])), i->i),
       children := (nt) -> Filtered(_pruned_PRF12_CT_Children(nt.params[1],nt.params[2], IPRDFT1, DFT1, IPRDFT2,
                                        (m, n, k)->_pruned_children(m, n, nt.params[4], (r, sp) -> PrunedIPRDFT(r, k mod n, nt.params[3], sp))),
                              lst -> ForAll(Filtered(lst, i->ObjId(i) = PrunedIPRDFT), j->j.params[4] <> [])),
       apply := (nt, C, cnt) -> let(N:=nt.params[1], k:=nt.params[2], m:=Rows(C[1]), n := N/m,
            _build_stack(m, n, nt.params[4], let(s1len := When(IsEvenInt(n), 3, 2), C{[s1len+1..Length(C)]}), nt.dims()[1], Scat, IterHStack1, HStack1, (a,b)->b*a) *
            _pruned_IPRF12_CT_Stage2(N, k, C, Diag(BHD(m,1,-1)), j->RC(Diag(fPrecompute(Twid(N,m,k,0,0,j+1)))))
       )
    ),
));

Class(IOPrunedConv, TaggedNonTerminal, rec(
    abbrevs := [
        (n,h,oblk,opat,iblk,ipat) -> Checked(IsPosIntSym(n), IsPosIntSym(oblk), IsList(opat), IsPosIntSym(iblk), IsList(ipat),
            AnySyms(n,iblk,oblk) or (IsInt(_unwrap(n)/_unwrap(iblk)) and IsInt(_unwrap(n)/_unwrap(oblk)) and
                ForAll(ipat, i->IsInt(i) and i < n/iblk) and ForAll(opat, i->IsInt(i) and i < n/oblk)),
            [_unwrap(n), h, _unwrap(oblk), opat, _unwrap(iblk), ipat, false]),
        (n,h,oblk,opat,iblk,ipat,isFreqData) -> Checked(IsPosIntSym(n), IsPosIntSym(oblk), IsList(opat), IsPosIntSym(iblk), IsList(ipat),
            AnySyms(n,iblk,oblk) or (IsInt(_unwrap(n)/_unwrap(iblk)) and IsInt(_unwrap(n)/_unwrap(oblk)) and
                ForAll(ipat, i->IsInt(i) and i < n/iblk) and ForAll(opat, i->IsInt(i) and i < n/oblk)),
            [_unwrap(n), h, _unwrap(oblk), opat, _unwrap(iblk), ipat, isFreqData])
        ],
    dims      := self >> [self.params[3]*Length(self.params[4]), self.params[5]*Length(self.params[6])],
    terminate := self >> let(size := self.params[1],
        taps := When(self.params[7], MatSPL(DFT(self.params[1], -1)) *
               Cond(IsList(self.params[2]), self.params[2], IsLambda(self.params[2]), self.params[2].tolist(), IsBound(self.params[2].list), self.params[2].list),
           self.params[2]),
        oblk := self.params[3], opat := self.params[4], iblk := self.params[5], ipat := self.params[6],
        Tensor(Mat(List(opat, i->BasisVec(size/oblk, i))), I(oblk)).terminate() *
        spiral.transforms.filtering.Circulant(size, taps, -size).terminate() *
        Tensor(Mat(List(ipat, i->BasisVec(size/iblk, i))).transpose(), I(iblk)).terminate()),
    isReal    := self >> false,
    normalizedArithCost := self >> let(n := self.params[1], IntDouble(5 * n * d_log(n) / d_log(2))),
    TType := TComplex,
    hashAs := self >> ApplyFunc(ObjId(self), [self.params[1], fUnk(self.params[2].range(), self.params[2].domain())]::Drop(self.params, 2))
));

NewRulesFor(IOPrunedConv, rec(
    IOPrunedConv_PrunedDFT_IPrunedDFT := rec(
       forTransposition := false,
       applicable :=  (self, nt) >> not nt.hasTags(),
       children := nt -> [[ PrunedIDFT(nt.params[1], 1, nt.params[3], nt.params[4]),
                            PrunedDFT(nt.params[1], -1, nt.params[5], nt.params[6]) ]],
       apply := (nt, C, cnt) -> Cond(ObjId(nt.params[2]) = var, # a variable is frequency domain data -> FData
                                    Checked(nt.params[7], C[1] * Diag(FData(nt.params[2])) * C[2]), # if data is provided it needs to be frequency domain
                                    ObjId(nt.params[2]) = Lambda,  # data is inside a larger array
                                    Checked(nt.params[7], C[1] * Diag(nt.params[2]) * C[2]),
                                    nt.params[7],   # if params[7] is true then data is frequency domain data
                                    C[1] * Diag(When(IsList(nt.params[2]), FList(TComplex, nt.params[2]), nt.params[2])) * C[2],
                                    let(cxfftdiag := 1/nt.params[1]*ComplexFFT(List(nt.params[2].tolist(), i->ComplexAny(_unwrap(i)))),
                                        C[1] * Diag(FData(cxfftdiag)) * C[2]))
    ),
));

#===========================================

Class(IOPrunedRConv, TaggedNonTerminal, rec(
    abbrevs := [
        (n,h,oblk,opat,iblk,ipat) -> Checked(IsPosIntSym(n), IsPosIntSym(oblk), IsList(opat), IsPosIntSym(iblk), IsList(ipat),
            AnySyms(n,iblk,oblk) or (IsInt(_unwrap(n)/_unwrap(iblk)) and IsInt(_unwrap(n)/_unwrap(oblk)) and
                ForAll(ipat, i->IsInt(i) and i < n/iblk) and ForAll(opat, i->IsInt(i) and i < n/oblk)),
            [_unwrap(n), h, _unwrap(oblk), opat, _unwrap(iblk), ipat, false]),
        (n,h,oblk,opat,iblk,ipat,isFreqData) -> Checked(IsPosIntSym(n), IsPosIntSym(oblk), IsList(opat), IsPosIntSym(iblk), IsList(ipat),
            AnySyms(n,iblk,oblk) or (IsInt(_unwrap(n)/_unwrap(iblk)) and IsInt(_unwrap(n)/_unwrap(oblk)) and
                ForAll(ipat, i->IsInt(i) and i < n/iblk) and ForAll(opat, i->IsInt(i) and i < n/oblk)),
            [_unwrap(n), h, _unwrap(oblk), opat, _unwrap(iblk), ipat, isFreqData])
        ],
    dims      := self >> [self.params[3]*Length(self.params[4]), self.params[5]*Length(self.params[6])],
    terminate := self >> let(size := self.params[1],
        taps := When(self.params[7], MatSPL(IPRDFT(self.params[1], -1)) * When(IsList(self.params[2]), self.params[2], self.params[2].list), self.params[2]),
        oblk := self.params[3], opat := self.params[4], iblk := self.params[5], ipat := self.params[6],
        Tensor(Mat(List(opat, i->BasisVec(size/oblk, i))), I(oblk)).terminate() *
        spiral.transforms.filtering.Circulant(size, taps, -size).terminate() *
        Tensor(Mat(List(ipat, i->BasisVec(size/iblk, i))).transpose(), I(iblk)).terminate()),
    isReal    := self >> true,
    normalizedArithCost := self >> let(n := self.params[1], IntDouble(5 * n * d_log(n) / d_log(2))),
    TType := TReal
));

NewRulesFor(IOPrunedRConv, rec(
    IOPrunedRConv_PrunedIPRDFT_PrunedPRDFT := rec(
       forTransposition := false,
       applicable :=  (self, nt) >> not nt.hasTags(),
       children := nt -> [[ PrunedIPRDFT(nt.params[1], 1, nt.params[3], nt.params[4]),
                            PrunedPRDFT(nt.params[1], -1, nt.params[5], nt.params[6]) ]],
       apply := (nt, C, cnt) -> Cond(ObjId(nt.params[2]) = var, # a variable is frequency domain data -> FData
                                Checked(nt.params[7], C[1] * RCDiag(FData(nt.params[2])) * C[2]), # if data is provided it needs to be frequency domain
                                nt.params[7],   # if params[7] is true then data is frequency domain data
                                C[1] *  RCDiag(When(IsList(nt.params[2]), FList(TReal, nt.params[2]), nt.params[2])) * C[2],
                                let(cxfftdiag := 1/nt.params[1]*ComplexFFT(List(nt.params[2].tolist(), i->ComplexAny(_unwrap(i)))),
                                    rcdiag := cxfftdiag{[1..Rows(PRDFT(nt.params[1], -1))/2]},
                                    rlist := Flat(List(rcdiag, i->[ReComplex(_unwrap(i)), ImComplex(_unwrap(i))])),
                                    C[1] * RCDiag(FList(TReal, rlist)) * C[2]))
    ),
));

_complexify := l -> List([1..Length(l)/2], i->l[2*i-1] + E(4) * l[2*i]);

#=========================================================================================================================================================
Declare(IOPrunedMDRConv);
Class(IOPrunedMDRConv, TaggedNonTerminal, rec(
    abbrevs := [
        (n,h,oblk,opat,iblk,ipat) -> Checked(ForAll(n, IsPosIntSym), IsPosIntSym(oblk), ForAll(opat, IsList), IsPosIntSym(iblk), ForAll(ipat, IsList),
            [_unwrap(n), h, _unwrap(oblk), opat, _unwrap(iblk), ipat, false]),
        (n,h,oblk,opat,iblk,ipat,isFreqData) -> Checked(ForAll(n, IsPosIntSym), IsPosIntSym(oblk), ForAll(opat, IsList), IsPosIntSym(iblk), ForAll(ipat, IsList),
            [_unwrap(n), h, _unwrap(oblk), opat, _unwrap(iblk), ipat, isFreqData])
        ],
    dims      := self >> [self.params[3]*Product(self.params[4], i->Length(i)), self.params[5]*Product(self.params[6], i->Length(i))],
    isReal    := self >> true,
    normalizedArithCost := self >> let(n := self.params[1], IntDouble(5 * n * d_log(n) / d_log(2))),
    TType := TReal,
    terminate := self >> When(self.params[7],
                            let(nlist := self.params[1],
                                n := nlist[1],
                                nfreq := n/2+1,
                                nrem := Drop(nlist, 1),
                                idft := Tensor(IPRDFT(n, -1), I(Product(nrem))) *
                                    Tensor(I(nfreq), L(2*Product(nrem), 2)) * Tensor(I(nfreq), RC(MDDFT(nrem, -1))),
                                tlist := MatSPL(idft) *
                                    Cond(IsList(self.params[2]), self.params[2], IsLambda(self.params[2]), self.params[2].tolist(), IsBound(self.params[2].list), self.params[2].list),
                                IOPrunedMDRConv(self.params[1], FList(TReal, tlist), self.params[3], self.params[4],
                                    self.params[5], self.params[6], false).terminate()
                            ),
                            let(nlist := self.params[1],
                                oblk := self.params[3], opat_md := self.params[4],
                                iblk := self.params[5], ipat_md := self.params[6],
                                scat3d := Tensor(List(Zip2(ipat_md, nlist), j->let(ipat := j[1], size := j[2],
                                    Tensor(Mat(List(ipat, i->BasisVec(size/iblk, i))).transpose(), I(iblk)).terminate()))::[Mat([[1], [0]])]),
                                gath3d := Tensor(List(Zip2(opat_md, nlist), j->let(opat := j[1], size := j[2],
                                    Tensor(Mat(List(opat, i->BasisVec(size/oblk, i))), I(oblk)).terminate()))::[Mat([[1, 0]])]),

                                dft3dr := RC(MDDFT(nlist, -1)),
                                idft3dr := RC(MDDFT(nlist, 1)),
                                gfd := List(1/Product(nlist) * MatSPL(MDDFT(nlist, 1)) * self.params[2].list, i->ComplexAny(_unwrap(i))),
                                gdiagr := RC(Diag(gfd)),
                                t := gath3d * idft3dr * gdiagr * dft3dr * scat3d,
                                t.terminate()
                            )
                        ),
    hashAs := self >> ApplyFunc(ObjId(self), [self.params[1], fUnk(self.params[2].range(), self.params[2].domain())]::Drop(self.params, 2))
));

NewRulesFor(IOPrunedMDRConv, rec(
    IOPrunedMDRConv_time2freq := rec(
       forTransposition := false,
       applicable :=  (self, nt) >> not nt.hasTags() and not nt.params[7],
       children := nt -> let(htime := nt.params[2].list,
                            nlist := nt.params[1],
                            dft := 1/Product(nlist) * MatSPL(MDDFT(nlist, 1)),
                            nfreq := nlist[1]/2+1,
                            hfreq := dft*htime,
                            hfreqc := List(hfreq, ComplexAny),
                            hfreqce := hfreqc{[1..nfreq*Product(Drop(nlist,1))]},
                            hfreqr := Flat(List(hfreqce, i->[ReComplex(i), ImComplex(i)])),
                            [[IOPrunedMDRConv(nt.params[1], FList(TReal, hfreqr), nt.params[3], nt.params[4], nt.params[5], nt.params[6], true)]]
                        ),
       apply := (nt, C, cnt) -> C[1]
    ),

    ## GPU/TITAN V Hockney algotithm variant
    ## 2-trip, 5-step, ZYX ====================================================
    IOPrunedMDRConv_3D_2trip_zyx_freqdata := rec(
       forTransposition := false,
       applicable :=  (self, nt) >> not nt.hasTags() and Length(nt.params[1]) = 3 and nt.params[7],
       children := nt -> let(nlist := nt.params[1],
                            diag := nt.params[2],
                            oblk := nt.params[3],
                            opats := nt.params[4],
                            iblk := nt.params[5],
                            ipats := nt.params[6],
                            nfreq := nlist[1]/2+1,
                            i := Ind(nfreq*nlist[2]),
                            hfunc := Cond(ObjId(diag) = Lambda,
                                let(j := Ind(nlist[3]),
                                    # Lambda(j, cxpack(diag.at(2*(j + i*nlist[3])), diag.at(2*(j + i*nlist[3])+1)))
                                    pos := i +j*nfreq*nlist[2],
                                    Lambda(j, cxpack(diag.at(2*pos), diag.at(2*pos+1)))
                                ),
                                ObjId(diag) = fUnk,
                                fUnk(TComplex, nlist[3]),
                                let(list := nt.params[2].list,  # here we assume FList(TReal, [...])
                                    clist := List([1..Length(list)/2], i->Cplx(list[2*i-1], list[2*i])),
                                    fc := FList(TComplex, clist),
                                    gf := fTensor(fBase(i), fId(nlist[3])),
                                    fCompose(fc, gf)
                                )
                            ),
                            [[ PrunedPRDFT(nlist[1], -1, iblk, ipats[1]),  # stage 1: PRDFT z
                                PrunedDFT(nlist[2], -1, iblk, ipats[2]),    # stage 2: DFT y
                                IOPrunedConv(nlist[3], hfunc, oblk, opats[3], iblk, ipats[3], true), # stage 3+4+5: complex conv in x
                                PrunedIDFT(nlist[2], 1, oblk, opats[2]), # stage 6: iDFT in y
                                PrunedIPRDFT(nlist[1], 1, oblk, opats[1]),   # stage 7: iPRDFT in z
                                InfoNt(i)
                            ]]),

       apply := (nt, C, cnt) -> let(prdft1d := C[1],
                                    pdft1d := C[2],
                                    iopconv := C[3],
                                    ipdft1d := C[4],
                                    iprdft1d := C[5],
                                    i := cnt[6].params[1],
                                    nlist := nt.params[1],
                                    n1 := nlist[1],
                                    nfreq := nlist[1]/2+1,
                                    n2 := nlist[2],
                                    n3 := nlist[3],
                                    oblk := nt.params[3],
                                    opats := nt.params[4],
                                    iblk := nt.params[5],
                                    ipats := nt.params[6],
                                    ns1 := iblk * Length(ipats[1]),
                                    ns2 := iblk * Length(ipats[2]),
                                    ns3 := iblk * Length(ipats[3]),
                                    nd1 := oblk * Length(opats[1]),
                                    nd2 := oblk * Length(opats[2]),
                                    nd3 := oblk * Length(opats[3]),
                                    stage1 := L(2*nfreq*ns3*ns2, ns3) * Tensor(I(ns2), Tensor(L(2*nfreq, 2) * prdft1d, I(ns3))) * Tensor(L(ns2*ns1, ns2), I(ns3)),
                                    stage2 := Tensor(I(ns3), Tensor(RC(pdft1d), I(nfreq))),
                                    pp := Tensor(L(ns3*n2*nfreq, n2*nfreq), I(2)) * Tensor(I(ns3), L(2*nfreq*n2, nfreq)),
                                    ppi := Tensor(I(nd3), L(2*nfreq*n2, 2*n2)) * Tensor(L(nd3*n2*nfreq, nd3), I(2)),
                                    stage543 := ppi * IDirSum(i, RC(iopconv)) * pp,
                                    stage76 := Tensor(L(nd2*nd1, nd1), I(nd3)) * Grp(Tensor((Tensor(I(nd2), iprdft1d * L(2*nfreq, nfreq)) *
                                        Tensor(RC(ipdft1d), I(nfreq))), I(nd3)) * L(2*nfreq*nd3*n2, 2*nfreq*n2)),
                                    conv3dr := stage76 * stage543 * stage2 * stage1,
                                    conv3dr
                            ),
    ),

    ## X dimension last (convolution)
    ## 5-step, ZYX ====================================================
    IOPrunedMDRConv_3D_5step_zyx_freqdata := rec(
       forTransposition := false,
       applicable :=  (self, nt) >> not nt.hasTags() and Length(nt.params[1]) = 3 and nt.params[7],
       children := nt -> let(nlist := nt.params[1],
                            diag := nt.params[2],
                            oblk := nt.params[3],
                            opats := nt.params[4],
                            iblk := nt.params[5],
                            ipats := nt.params[6],
                            nfreq := nlist[1]/2+1,
                            i := Ind(nfreq*nlist[2]),
                            hfunc := Cond(ObjId(diag) = Lambda,
                                let(j := Ind(nlist[3]),
                                    Lambda(j, cxpack(diag.at(2*(j + i*nlist[3])), diag.at(2*(j + i*nlist[3])+1)))
                                ),
                                ObjId(diag) = fUnk,
                                fUnk(TComplex, nlist[3]),
                                let(list := nt.params[2].list,  # here we assume FList(TReal, [...])
                                    clist := List([1..Length(list)/2], i->Cplx(list[2*i-1], list[2*i])),
                                    fc := FList(TComplex, clist),
                                    gf := fTensor(fBase(i), fId(nlist[3])),
                                    fCompose(fc, gf)
                                )
                            ),
                            [[ PrunedPRDFT(nlist[1], -1, iblk, ipats[1]),  # stage 1: PRDFT z
                                PrunedDFT(nlist[2], -1, iblk, ipats[2]),    # stage 2: DFT y
                                IOPrunedConv(nlist[3], hfunc, oblk, opats[3], iblk, ipats[3], true), # stage 3+4+5: complex conv in x
                                PrunedIDFT(nlist[2], 1, oblk, opats[2]), # stage 6: iDFT in y
                                PrunedIPRDFT(nlist[1], 1, oblk, opats[1]),   # stage 7: iPRDFT in z
                                InfoNt(i)
                            ]]),

       apply := (nt, C, cnt) -> let(prdft1d := Grp(C[1]),
                                    pdft1d := Grp(C[2]),
                                    iopconv := Grp(C[3]),
                                    ipdft1d := Grp(C[4]),
                                    iprdft1d := Grp(C[5]),
                                    i := cnt[6].params[1],
                                    nlist := nt.params[1],
                                    n1 := nlist[1],
                                    nfreq := nlist[1]/2+1,
                                    n2 := nlist[2],
                                    n3 := nlist[3],
                                    oblk := nt.params[3],
                                    opats := nt.params[4],
                                    iblk := nt.params[5],
                                    ipats := nt.params[6],
                                    ns1 := iblk * Length(ipats[1]),
                                    ns2 := iblk * Length(ipats[2]),
                                    ns3 := iblk * Length(ipats[3]),
                                    nd1 := oblk * Length(opats[1]),
                                    nd2 := oblk * Length(opats[2]),
                                    nd3 := oblk * Length(opats[3]),
                                    stage1 := Tensor(I(nfreq), L(2*ns2*ns3, ns2*ns3)) * Tensor(prdft1d, I(ns2*ns3)),
                                    stage2 := RC(Tensor(I(nfreq), pdft1d, I(ns3))),
                                    stage543c := IDirSum(i, iopconv),
                                    stage543 := RC(stage543c),
                                    stage6 := RC(Tensor(I(nfreq), ipdft1d, I(nd3))),
                                    stage7 := Tensor(iprdft1d, I(nd2*nd3)) * Tensor(I(nfreq), L(2*nd2*nd3, 2)),
                                    conv3dr := stage7 * stage6 * stage543 * stage2 * stage1,
                                    conv3dr
                            ),
    ),
    ## 5-step, YZX ====================================================
    IOPrunedMDRConv_3D_5step_yzx_freqdata := rec(
       forTransposition := false,
       applicable :=  (self, nt) >> false, #not nt.hasTags() and Length(nt.params[1]) = 3 and nt.params[7], Breaks!
       children := nt -> let(nlist := nt.params[1],
                            diag := nt.params[2],
                            oblk := nt.params[3],
                            opats := nt.params[4],
                            iblk := nt.params[5],
                            ipats := nt.params[6],
                            nfreq := nlist[1]/2+1,
                            i := Ind(nfreq*nlist[2]),
                            hfunc := Cond(ObjId(diag) = Lambda,
                                let(j := Ind(nlist[3]),
                                    Lambda(j, cxpack(diag.at(2*(j + i*nlist[3])), diag.at(2*(j + i*nlist[3])+1)))
                                ),
                                ObjId(diag) = fUnk,
                                fUnk(TComplex, nlist[3]),
                                let(list := nt.params[2].list,  # here we assume FList(TReal, [...])
                                    clist := List([1..Length(list)/2], i->Cplx(list[2*i-1], list[2*i])),
                                    fc := FList(TComplex, clist),
                                    gf := fTensor(fBase(i), fId(nlist[3])),
                                    fCompose(fc, gf)
                                )
                            ),
                            [[ PrunedPRDFT(nlist[2], -1, iblk, ipats[2]),  # stage 1: PRDFT y
                                PrunedDFT(nlist[1], -1, iblk, ipats[1]),    # stage 2: DFT z
                                IOPrunedConv(nlist[3], hfunc, oblk, opats[3], iblk, ipats[3], true), # stage 3+4+5: complex conv in x
                                PrunedIDFT(nlist[1], 1, oblk, opats[1]), # stage 6: iDFT in z
                                PrunedIPRDFT(nlist[2], 1, oblk, opats[2]),   # stage 7: iPRDFT in y
                                InfoNt(i)
                            ]]),

       apply := (nt, C, cnt) -> let(prdft1d := C[1],
                                    pdft1d := C[2],
                                    iopconv := C[3],
                                    ipdft1d := C[4],
                                    iprdft1d := C[5],
                                    i := cnt[6].params[1],
                                    nlist := nt.params[1],
                                    n1 := nlist[1],
                                    nfreq := nlist[2]/2+1,
                                    n2 := nlist[2],
                                    n3 := nlist[3],
                                    oblk := nt.params[3],
                                    opats := nt.params[4],
                                    iblk := nt.params[5],
                                    ipats := nt.params[6],
                                    ns1 := iblk * Length(ipats[1]),
                                    ns2 := iblk * Length(ipats[2]),
                                    ns3 := iblk * Length(ipats[3]),
                                    nd1 := oblk * Length(opats[1]),
                                    nd2 := oblk * Length(opats[2]),
                                    nd3 := oblk * Length(opats[3]),
                                    stage1 := Tensor(I(nfreq*ns2), L(2*ns3, ns3)) * Tensor(I(ns2), prdft1d, I(ns3)),
                                    stage2 := RC(Tensor(pdft1d, I(nfreq*ns3))),
                                    stage543c := IDirSum(i, iopconv),
                                    stage543 := RC(stage543c),
                                    stage6 := RC(Tensor(ipdft1d, I(nfreq*nd3))),
                                    stage7 := Tensor(I(nd2), iprdft1d, I(nd3)) * Tensor(I(nfreq*nd2), L(2*nd3, 2)),
                                    conv3dr := stage7 * stage6 * stage543 * stage2 * stage1,
                                    conv3dr
                            ),
    ),
    ## 3-step, (ZY)X ====================================================
    IOPrunedMDRConv_3D_3step_zyx_freqdata := rec(
       forTransposition := false,
       applicable :=  (self, nt) >> not nt.hasTags() and Length(nt.params[1]) = 3 and nt.params[7],
       children := nt -> let(nlist := nt.params[1],
                            diag := nt.params[2],
                            oblk := nt.params[3],
                            opats := nt.params[4],
                            iblk := nt.params[5],
                            ipats := nt.params[6],
                            nfreq := nlist[1]/2+1,
                            i := Ind(nfreq*nlist[2]),
                            hfunc := Cond(ObjId(diag) = Lambda,
                                let(j := Ind(nlist[3]),
                                    Lambda(j, cxpack(diag.at(2*(j + i*nlist[3])), diag.at(2*(j + i*nlist[3])+1)))
                                ),
                                ObjId(diag) = fUnk,
                                fUnk(TComplex, nlist[3]),
                                let(list := nt.params[2].list,  # here we assume FList(TReal, [...])
                                    clist := List([1..Length(list)/2], i->Cplx(list[2*i-1], list[2*i])),
                                    fc := FList(TComplex, clist),
                                    gf := fTensor(fBase(i), fId(nlist[3])),
                                    fCompose(fc, gf)
                                )
                            ),
                            [[ PrunedPRDFT(nlist[1], -1, iblk, ipats[1]),  # stage 1a: PRDFT z
                                PrunedDFT(nlist[2], -1, iblk, ipats[2]),    # stage 1b: DFT y
                                IOPrunedConv(nlist[3], hfunc, oblk, opats[3], iblk, ipats[3], true), # stage 3+4+5: complex conv in x
                                PrunedIDFT(nlist[2], 1, oblk, opats[2]), # stage 6b: iDFT in y
                                PrunedIPRDFT(nlist[1], 1, oblk, opats[1]),   # stage 6a: iPRDFT in z
                                InfoNt(i)
                            ]]),

       apply := (nt, C, cnt) -> let(prdft1d := Grp(C[1]),
                                    pdft1d := Grp(C[2]),
                                    iopconv := Grp(C[3]),
                                    ipdft1d := Grp(C[4]),
                                    iprdft1d := Grp(C[5]),
                                    i := cnt[6].params[1],
                                    nlist := nt.params[1],
                                    n1 := nlist[1],
                                    nfreq := nlist[1]/2+1,
                                    n2 := nlist[2],
                                    n3 := nlist[3],
                                    oblk := nt.params[3],
                                    opats := nt.params[4],
                                    iblk := nt.params[5],
                                    ipats := nt.params[6],
                                    ns1 := iblk * Length(ipats[1]),
                                    ns2 := iblk * Length(ipats[2]),
                                    ns3 := iblk * Length(ipats[3]),
                                    nd1 := oblk * Length(opats[1]),
                                    nd2 := oblk * Length(opats[2]),
                                    nd3 := oblk * Length(opats[3]),
                                    stage1 := Tensor(I(nfreq), L(2*ns2, ns2)) * Tensor(prdft1d, I(ns2)),
                                    stage2 := RC(Tensor(I(nfreq), pdft1d)),
                                    stage12 := Tensor(I(nfreq * n2), L(2*ns3, ns3)) * Tensor(stage2 * stage1, I(ns3)),
                                    stage543c := IDirSum(i, iopconv),
                                    stage543 := RC(stage543c),
                                    stage6 := RC(Tensor(I(nfreq), ipdft1d)),
                                    stage7 := Tensor(iprdft1d, I(nd2)) * Tensor(I(nfreq), L(2*nd2, 2)),
                                    stage67 := Tensor(stage7 * stage6, I(nd3)) * Tensor(I(nfreq * n2), L(2*nd3, 2)),
                                    conv3dr := stage67 * stage543 * stage12,
                                    conv3dr
                            ),
    ),
    ## 3-step, (YZ)X ====================================================
    IOPrunedMDRConv_3D_3step_yzx_freqdata := rec(
       forTransposition := false,
       applicable :=  (self, nt) >> false, #not nt.hasTags() and Length(nt.params[1]) = 3 and nt.params[7], Breaks!
       children := nt -> let(nlist := nt.params[1],
                            diag := nt.params[2],
                            oblk := nt.params[3],
                            opats := nt.params[4],
                            iblk := nt.params[5],
                            ipats := nt.params[6],
                            nfreq := nlist[1]/2+1,
                            i := Ind(nfreq*nlist[2]),
                            hfunc := Cond(ObjId(diag) = Lambda,
                                let(j := Ind(nlist[3]),
                                    Lambda(j, cxpack(diag.at(2*(j + i*nlist[3])), diag.at(2*(j + i*nlist[3])+1)))
                                ),
                                ObjId(diag) = fUnk,
                                fUnk(TComplex, nlist[3]),
                                let(list := nt.params[2].list,  # here we assume FList(TReal, [...])
                                    clist := List([1..Length(list)/2], i->Cplx(list[2*i-1], list[2*i])),
                                    fc := FList(TComplex, clist),
                                    gf := fTensor(fBase(i), fId(nlist[3])),
                                    fCompose(fc, gf)
                                )
                            ),
                            [[ PrunedPRDFT(nlist[2], -1, iblk, ipats[2]),  # stage 1a: PRDFT y
                                PrunedDFT(nlist[1], -1, iblk, ipats[1]),    # stage 1b: DFT z
                                IOPrunedConv(nlist[3], hfunc, oblk, opats[3], iblk, ipats[3], true), # stage 3+4+5: complex conv in x
                                PrunedIDFT(nlist[1], 1, oblk, opats[1]), # stage 6b: iDFT in z
                                PrunedIPRDFT(nlist[2], 1, oblk, opats[2]),   # stage 6a: iPRDFT in y
                                InfoNt(i)
                            ]]),

       apply := (nt, C, cnt) -> let(prdft1d := C[1],
                                    pdft1d := C[2],
                                    iopconv := C[3],
                                    ipdft1d := C[4],
                                    iprdft1d := C[5],
                                    i := cnt[6].params[1],
                                    nlist := nt.params[1],
                                    n1 := nlist[1],
                                    nfreq := nlist[2]/2+1,
                                    n2 := nlist[2],
                                    n3 := nlist[3],
                                    oblk := nt.params[3],
                                    opats := nt.params[4],
                                    iblk := nt.params[5],
                                    ipats := nt.params[6],
                                    ns1 := iblk * Length(ipats[1]),
                                    ns2 := iblk * Length(ipats[2]),
                                    ns3 := iblk * Length(ipats[3]),
                                    nd1 := oblk * Length(opats[1]),
                                    nd2 := oblk * Length(opats[2]),
                                    nd3 := oblk * Length(opats[3]),
                                    stage1 := Tensor(I(ns1), prdft1d),
                                    stage2 := RC(Tensor(pdft1d, I(nfreq))),
                                    stage12 := Tensor(I(nfreq * n2), L(2*ns3, ns3)) * Tensor(stage2 * stage1, I(ns3)),
                                    stage543c := IDirSum(i, iopconv),
                                    stage543 := RC(stage543c),
                                    stage6 := RC(Tensor(ipdft1d, I(nfreq))),
                                    stage7 := Tensor(I(nd1), iprdft1d),
                                    stage67 := Tensor(stage7 * stage6, I(nd3)) * Tensor(I(nfreq * n2), L(2*nd3, 2)),
                                    conv3dr := stage67 * stage543 * stage12,
                                    conv3dr
                            ),
    ),
#======================

    IOPrunedMDRConv_2D_2trip_xy_freqdata := rec(
       forTransposition := true,
       applicable :=  (self, nt) >> not nt.hasTags() and Length(nt.params[1]) = 2 and nt.params[7],
       children := nt -> let(nlist := nt.params[1],
                            diag := nt.params[2],
                            oblk := nt.params[3],
                            opats := nt.params[4],
                            iblk := nt.params[5],
                            ipats := nt.params[6],
                            nfreq := nlist[2]/2+1,
                            i := Ind(nfreq),
                            hfunc := Cond(ObjId(diag) = Lambda,
                                let(j := Ind(nlist[1]),
                                    pos := i + j*nfreq,
                                    Lambda(j, cxpack(diag.at(2*pos), diag.at(2*pos+1)))
                                ),
                                ObjId(diag) = fUnk,
                                fUnk(TComplex, nlist[1]),
                                let(list := nt.params[1].list,  # here we assume FList(TReal, [...])
                                    clist := List([1..Length(list)/2], i->Cplx(list[2*i-1], list[2*i])),
                                    fc := FList(TComplex, clist),
                                    gf := fTensor(fBase(i), fId(nlist[1])),
                                    fCompose(fc, gf)
                                )
                            ),
                            [[ PrunedPRDFT(nlist[2], -1, iblk, ipats[2]),  # stage 1: PRDFT x
                                IOPrunedConv(nlist[1], hfunc, oblk, opats[1], iblk, ipats[1], true), # stage 2+3+4: complex conv in y
                                PrunedIPRDFT(nlist[2], 1, oblk, opats[1]),   # stage 5: iPRDFT in x
                                InfoNt(i)
                            ]]),

       apply := (nt, C, cnt) -> let(prdft1d := C[1],
                                    iopconv := C[2],
                                    iprdft1d := C[3],
                                    i := cnt[4].params[1],
                                    nlist := nt.params[1],
                                    n1 := nlist[1],
                                    nfreq := nlist[1]/2+1,
                                    n2 := nlist[2],
                                    oblk := nt.params[3],
                                    opats := nt.params[4],
                                    iblk := nt.params[5],
                                    ipats := nt.params[6],
                                    ns1 := iblk * Length(ipats[1]),
                                    ns2 := iblk * Length(ipats[2]),
                                    nd1 := oblk * Length(opats[1]),
                                    nd2 := oblk * Length(opats[2]),

#                                    stage1 := L(2*nfreq*ns3*ns2, ns3) * Tensor(I(ns2), Tensor(L(2*nfreq, 2) * prdft1d, I(ns3))) * Tensor(L(ns2*ns1, ns2), I(ns3)),
#                                    stage2 := Tensor(I(ns3), Tensor(RC(pdft1d), I(nfreq))),
#                                    pp := Tensor(L(ns3*n2*nfreq, n2*nfreq), I(2)) * Tensor(I(ns3), L(2*nfreq*n2, nfreq)),
#                                    ppi := Tensor(I(nd3), L(2*nfreq*n2, 2*n2)) * Tensor(L(nd3*n2*nfreq, nd3), I(2)),
#                                    stage543 := ppi * IDirSum(i, RC(iopconv)) * pp,
#                                    stage76 := Tensor(L(nd2*nd1, nd1), I(nd3)) * Grp(Tensor((Tensor(I(nd2), iprdft1d * L(2*nfreq, nfreq)) *
#                                        Tensor(RC(ipdft1d), I(nfreq))), I(nd3)) * L(2*nfreq*nd3*n2, 2*nfreq*n2)),

                                    stage1 := Tensor(I(ns2), prdft1d),
                                    pp := Tensor(L(ns2*nfreq, nfreq), I(2)),
                                    ppi := Tensor(L(nd2*nfreq, nd2), I(2)),
                                    stage432 := IDirSum(i, RC(iopconv)),
                                    stage5 :=  Tensor(I(nd2), iprdft1d),

                                    conv2dr := Grp(stage5 * ppi) * stage432 * Grp(pp * stage1),
                                    conv2dr
                            ),
    ),
#     IOPrunedMDRConv_2D_2trip_yx_freqdata := rec(
#       forTransposition := true,
#       applicable :=  (self, nt) >> not nt.hasTags() and Length(nt.params[1]) = 2 and nt.params[7],
#       children := nt -> let(nlist := nt.params[1],
#                            diag := nt.params[2],
#                            oblk := nt.params[3],
#                            opats := nt.params[4],
#                            iblk := nt.params[5],
#                            ipats := nt.params[6],
#                            nfreq := nlist[1]/2+1,
#                            i := Ind(nfreq),
#                            hfunc := Cond(ObjId(diag) = Lambda,
#                                let(j := Ind(nlist[2]),
#                                    pos := i + j*nfreq,
#                                    Lambda(j, cxpack(diag.at(2*pos), diag.at(2*pos+1)))
#                                ),
#                                ObjId(diag) = fUnk,
#                                fUnk(TComplex, nlist[2]),
#                                let(list := nt.params[2].list,  # here we assume FList(TReal, [...])
#                                    clist := List([1..Length(list)/2], i->Cplx(list[2*i-1], list[2*i])),
#                                    fc := FList(TComplex, clist),
#                                    gf := fTensor(fBase(i), fId(nlist[2])),
#                                    fCompose(fc, gf)
#                                )
#                            ),
#                            [[ PrunedPRDFT(nlist[1], -1, iblk, ipats[1]),  # stage 1: PRDFT y
#                                IOPrunedConv(nlist[2], hfunc, oblk, opats[2], iblk, ipats[2], true), # stage 2+3+4: complex conv in x
#                                PrunedIPRDFT(nlist[1], 1, oblk, opats[1]),   # stage 5: iPRDFT in y
#                                InfoNt(i)
#                            ]]),
#
#       apply := (nt, C, cnt) -> let(prdft1d := C[1],
#                                    iopconv := C[2],
#                                    iprdft1d := C[3],
#                                    i := cnt[4].params[1],
#                                    nlist := nt.params[1],
#                                    n1 := nlist[1],
#                                    nfreq := nlist[1]/2+1,
#                                    n2 := nlist[2],
#                                    oblk := nt.params[3],
#                                    opats := nt.params[4],
#                                    iblk := nt.params[5],
#                                    ipats := nt.params[6],
#                                    ns1 := iblk * Length(ipats[1]),
#                                    ns2 := iblk * Length(ipats[2]),
#                                    nd1 := oblk * Length(opats[1]),
#                                    nd2 := oblk * Length(opats[2]),
#
##                                    stage1 := L(2*nfreq*ns3*ns2, ns3) * Tensor(I(ns2), Tensor(L(2*nfreq, 2) * prdft1d, I(ns3))) * Tensor(L(ns2*ns1, ns2), I(ns3)),
##                                    stage2 := Tensor(I(ns3), Tensor(RC(pdft1d), I(nfreq))),
##                                    pp := Tensor(L(ns3*n2*nfreq, n2*nfreq), I(2)) * Tensor(I(ns3), L(2*nfreq*n2, nfreq)),
##                                    ppi := Tensor(I(nd3), L(2*nfreq*n2, 2*n2)) * Tensor(L(nd3*n2*nfreq, nd3), I(2)),
##                                    stage543 := ppi * IDirSum(i, RC(iopconv)) * pp,
##                                    stage76 := Tensor(L(nd2*nd1, nd1), I(nd3)) * Grp(Tensor((Tensor(I(nd2), iprdft1d * L(2*nfreq, nfreq)) *
##                                        Tensor(RC(ipdft1d), I(nfreq))), I(nd3)) * L(2*nfreq*nd3*n2, 2*nfreq*n2)),
#
#                                    stage1 := L(2*nfreq*ns3*ns2, ns3) * Tensor(I(ns2), Tensor(L(2*nfreq, 2) * prdft1d, I(ns3))) * Tensor(L(ns2*ns1, ns2), I(ns3)),
#                                    pp := Tensor(L(ns3*n2*nfreq, n2*nfreq), I(2)) * Tensor(I(ns3), L(2*nfreq*n2, nfreq)),
#                                    ppi := Tensor(I(nd3), L(2*nfreq*n2, 2*n2)) * Tensor(L(nd3*n2*nfreq, nd3), I(2)),
#                                    stage432 := ppi * IDirSum(i, RC(iopconv)) * pp,
#                                    stage5 := Tensor(L(nd2*nd1, nd1), I(nd3)) * Grp(Tensor((Tensor(I(nd2), iprdft1d * L(2*nfreq, nfreq)) *
#                                        Tensor(RC(ipdft1d), I(nfreq))), I(nd3)) * L(2*nfreq*nd3*n2, 2*nfreq*n2)),
#
#                                    conv2dr := stage5 * stage432 * stage1,
#                                    conv2dr
#                            ),
#    ),

));


############################################################################
##
##  recognize.gi                      IRREDSOL                         Burkhard Höfling
##
##  Copyright © 2003–2016 Burkhard Höfling
##

############################################################################
##
#F  IsAvailableIdIrreducibleSolubleMatrixGroup(<G>)
##
##  see the IRREDSOL manual
##
InstallGlobalFunction(IsAvailableIdIrreducibleSolubleMatrixGroup,
    function(G)
        return IsMatrixGroup(G) and IsFinite(FieldOfMatrixGroup(G))
            and IsSolvableGroup(G)  and IsIrreducibleMatrixGroup(G)
            and IsAvailableIrreducibleSolubleGroupData(
                DegreeOfMatrixGroup(G), Size(TraceField(G)));
    end);

############################################################################
##
#F  IsAvailableIdAbsolutelyIrreducibleSolubleMatrixGroup(<G>)
##
##  see the IRREDSOL manual
##
InstallGlobalFunction(IsAvailableIdAbsolutelyIrreducibleSolubleMatrixGroup,
    function(G)
        return IsMatrixGroup(G) and IsFinite(FieldOfMatrixGroup(G))
            and IsSolvableGroup(G) and IsAbsolutelyIrreducibleMatrixGroup(G)
            and IsAvailableAbsolutelyIrreducibleSolubleGroupData(
                DegreeOfMatrixGroup(G), Size(TraceField(G)));
    end);

############################################################################
##
#F  FingerprintDerivedSeries(<n>)
##
InstallMethod(FingerprintDerivedSeries, "generic method for finite group", true,
    [IsGroup and IsFinite], 0,
    function(G)
        local EncodedPrimePower, EncodedAbInv, primes, max, der;

        EncodedPrimePower := function(n, primes)

            local res, i, p;

            res := -1; # count p^1 as zero

            i := 1;
            while n mod primes[i][1] <> 0 do
                 res := res + primes[i][2];
                 i := i + 1;
            od;
            p := primes[i][1];
            repeat
                    n := n / p;
                    res := res + 1;
            until n mod p <> 0;

            if n <> 1 then
                Error("cannot encode prime power n");
            fi;
            return res;
        end;

        EncodedAbInv := function (inv, primes, max)

            local i, res;
            res := EncodedPrimePower(inv[1], primes);
            for i in [2..Length(inv)] do
                res := res * max + EncodedPrimePower(inv[i], primes);
            od;
            return res;
        end;

        primes := Collected(FactorsInt(Size(G)));
        max := Sum(primes, p -> p[2]);
        der := DerivedSeries(G);
        return List(der{[1..Length(der)-1]}, N -> EncodedAbInv(AbelianInvariants(N), primes, max));
    end);

RedispatchOnCondition(FingerprintDerivedSeries, true,
    [IsGroup], [IsFinite], 0);

############################################################################
##
#M  FingerprintMatrixGroup(<G>)
##
InstallMethod(FingerprintMatrixGroup, "for irreducible FFE matrix group", true,
    [IsMatrixGroup and CategoryCollections(IsFFECollColl)], 0,
    function(G)

    local EncodedPrimeDecomposition, primes, max, ids, len, ccl, cl, id, pos, rep, i, g, q,
        hsize, hstep, counts, fppoly;

    EncodedPrimeDecomposition := function(n, primes)

        local res, i, p;

        res := 0;

        for i in [1..Length(primes)] do
            res := res * (primes[i][2]+1);
            p := primes[i][1];
            while n mod p = 0 do
                n := n / p;
                res := res + 1;
            od;
        od;
        if n <> 1 then
            Error("cannot encode integer n");
        fi;
        return res;
    end;

    primes := Collected(FactorsInt(Size(G)));
    max := Product(primes, p -> p[2]+1);
    q := Size(TraceField(G));
    rep := RepresentationIsomorphism(G);
    ccl := AttributeValueNotSet(ConjugacyClasses, Source(rep));
    hsize := NextPrimeInt(2*Length(ccl));
    hstep := NextPrimeInt(RootInt(hsize));
    ids := EmptyPlist(hsize);
    counts := EmptyPlist(hsize);
    len := 0;
    for cl in ccl do
        g := Representative(cl);
        id := EncodedPrimeDecomposition(Size(cl), primes)
                + max * (EncodedPrimeDecomposition(Order(g), primes)
                        + max * CodeCharacteristicPolynomial(ImageElm(rep, g), q));
        pos := id mod hsize + 1;
        while IsBound(ids[pos]) and ids[pos] <> id do
            pos := pos + hstep;
            if pos > hsize then
                pos := pos - hsize;
            fi;
        od;
        if IsBound(ids[pos]) then
            counts[pos] := counts[pos] + 1;
        else
            ids[pos] := id;
            counts[pos] := 1;
        fi;
    od;
    fppoly := [];
    for id in AsSet(ids) do
        pos := id mod hsize + 1;
        while IsBound(ids[pos]) and ids[pos] <> id do
            pos := pos + hstep;
            if pos > hsize then
                pos := pos - hsize;
            fi;
        od;
        Add(fppoly, [id, counts[pos]]);
    od;
    return fppoly;
end);

############################################################################
##
#F  ConjugatingMatIrreducibleRepOrFail(repG, repH, q, linonly, maxcost, limit_CMIROF)
##
##  computes a record x such that G^x.iso = H and (G^homG)^x.mat = H^homH.
##  If no such matrix exists, the function returns fail.
##
##  maxcost and limit_CMIROF specify when the function will abort the search, returning
##  the string "too expensive".
##
##  This works like the GAP function Morphium but only looks for linear
##  isomorphisms.
##
InstallGlobalFunction(ConjugatingMatIrreducibleRepOrFail,
    function(repG, repH, q, maxcost, limit_CMIROF)

    local EncodedPrimeDecomposition, G, H, ccl, cl, hsize, hstep, ids, id, primes,
        fac, gens, genspos, imglist, g, i, j, k, len,
        cost, weight, mingens, mincost, mingenspos, totalcost, pos, d, ind, imgs, hom, count,
        sizes, D, proj, homG, homH, group, cent, C, centsize, imgreps, matrep, orb, S, occ, cpH,
        moduleG, moduleH, dirr, mat;

    EncodedPrimeDecomposition := function(n, primes)

        local res, i, p;

        res := 0;

        for i in [1..Length(primes)] do
            res := res * (primes[i][2]+1);
            p := primes[i][1];
            while n mod p = 0 do
                n := n / p;
                res := res + 1;
            od;
        od;
        if n <> 1 then
            Error("cannot encode integer n");
        fi;
        return res;
    end;

    G := repG.group;
    H := repH.group;
    primes := Collected(FactorsInt(Size(G)));
    fac := Sum(primes, p -> p[2]);
    if not IsBound(repG.classes) then
        repG.classes := AttributeValueNotSet(ConjugacyClasses, G);
    fi;
    ccl := repG.classes;
    hsize := NextPrimeInt(2*Length(ccl));
    hstep := NextPrimeInt(RootInt(hsize));
    ids := EmptyPlist(hsize);
    occ := EmptyPlist(hsize);
    cost := EmptyPlist(hsize);
    len := 0;
    for i in [1..Length(ccl)] do
        cl := ccl[i];
        id := [Size(cl), Order(Representative(cl)),
            CodeCharacteristicPolynomial(ImageElm(repG.rep, Representative(cl)), q)];
        pos :=((id[3] * fac + EncodedPrimeDecomposition(id[1], primes)) * fac
                + EncodedPrimeDecomposition(id[2], primes)) mod hsize + 1;
        while IsBound(ids[pos]) and ids[pos]<>id do
            pos := pos + hstep;
            if pos > hsize then
                pos := pos - hsize;
            fi;
        od;
        if IsBound(ids[pos]) then
            Add(occ[pos], i);
        else
            ids[pos] := id;
            occ[pos] := [i];
        fi;
    od;

    for i in [1..Length(ids)] do
        if IsBound(ids[i]) then
            cost[i] := ids[i][1]*Length(occ[i]);
        fi;
    od;

    gens := ShallowCopy(MinimalGeneratingSet(G));
    Info(InfoMorph, 1, "ConjugatingMat: MinimalGeneratingSet has ", Length(gens), " generators");

    mincost := 1;
    mingenspos := [];

    for i in [1..Length(gens)] do
        g := gens[i];
        id := [Size(ConjugacyClass(G, g)), Order(g),
            CodeCharacteristicPolynomial(ImageElm(repG.rep, g), q)];
        pos :=((id[3] * fac + EncodedPrimeDecomposition(id[1], primes)) * fac
                + EncodedPrimeDecomposition(id[2], primes)) mod hsize + 1;
        while IsBound(ids[pos]) and ids[pos]<>id do
            pos := pos + hstep;
            if pos > hsize then
                pos := pos - hsize;
            fi;
        od;
        mincost := mincost + cost[pos];
        Add(mingenspos, pos);
    od;
    mingens := gens;

    Info(InfoMorph, 1, "ConjugatingMat: cost of minimal generating system is ", mincost);

    # try to find better generators

    if mincost > maxcost or mincost > 100 then
        weight := EmptyPlist(hsize);
        weight[1] := 0;
        for i in [2..Length(ids)] do
            if IsBound(cost[i]) then
                weight[i] := weight[i-1] + QuoInt(Size(G), cost[i]);
            else
                weight[i] := weight[i-1];
            fi;
        od;

        for i in [1..LogInt(Size(G),2)*Length(MinimalGeneratingSet(G))] do
            gens := [];
            S := TrivialSubgroup(G);
            totalcost := 1;
            genspos := [];
            repeat
                if Random([1..10]) = 1 then
                    g := Random(G);
                    id := [Size(ConjugacyClass(G, g)), Order(g),
                        CodeCharacteristicPolynomial(ImageElm(repG.rep, g), q)];
                    pos :=((id[3] * fac + EncodedPrimeDecomposition(id[1], primes)) * fac
                            + EncodedPrimeDecomposition(id[2], primes)) mod hsize + 1;
                    while IsBound(ids[pos]) and ids[pos]<>id do
                        pos := pos + hstep;
                        if pos > hsize then
                            pos := pos - hsize;
                        fi;
                    od;
                else
                    k := Random(1, weight[Length(weight)]);
                    pos := PositionSorted(weight, k);
                    g := Random(ccl[Random(occ[pos])]);
                fi;
                if not g in S then
                    Add(gens, g);
                    Add(genspos, pos);
                    totalcost := totalcost + cost[pos];
                    S := ClosureGroup(S, g);
                    #remove for redundant generators - we need not check g
                    j := 1;
                    repeat
                        while j < Length(gens) and Size(Subgroup(S, gens{Difference([1..Length(gens)], [j])})) < Size(S) do
                            j := j + 1;
                        od;
                        if j < Length(gens) then
                            totalcost := totalcost-cost[genspos[j]];
                            Remove(gens, j);
                            Remove(genspos, j);
                        fi;
                    until j >= Length(gens);
                    if totalcost > mincost then
                        break;
                    fi;
                fi;
            until Size(S) = Size(G);

            if totalcost < mincost then
                mingens := gens;
                mincost := totalcost;
                mingenspos := genspos;
                Info(InfoMorph, 2, "ConjugatingMat: found better generating system of cost ", mincost, " after ", i, " attempts");
            else
                Info(InfoMorph, 3, "ConjugatingMat: found generating system of cost ", totalcost);
            fi;
        od;
    fi;

    if mincost > maxcost then
        Info(InfoMorph, 1, "ConjugatingMat: too expoensive");
        return "too expensive";
    fi;
    Info(InfoMorph, 1, "ConjugatingMat: using generating system of cost ", mincost);

    if not IsBound(repH.classes) then
        repH.classes := AttributeValueNotSet(ConjugacyClasses, H);
    fi;
    ccl := repH.classes;

    # compute set of possible generator images

    imglist := [];
    cpH := [];

    # sort, most expensive first (one representative will be picked), then ascending cost
    SortParallel(mingenspos, mingens, function(i, j) return cost[i] > cost[j]; end);

    for i in [1..Length(mingens)] do
        id := ids[mingenspos[i]];
        imglist[i] := [];
        count := Length(occ[mingenspos[i]]);
        for j in [1..Length(ccl)] do
            cl := ccl[j];
            if Size(cl) = id[1] and Order(Representative(cl)) = id[2] then
                if not IsBound(cpH[j]) then
                    cpH[j] := CodeCharacteristicPolynomial(ImageElm(repH.rep, Representative(cl)), q);
                fi;
                if cpH[j] = id[3] then
                    if i = 1 then
                        Add(imglist[i], Representative(cl));
                    else
                        Append(imglist[i], AttributeValueNotSet(AsList, cl));
                    fi;
                    count := count - 1;

                    # if H contains more such conjugacy classes, the groups cannot be
                    # isomorphic so it doesn't matter if we miss some possible images
                    if count = 0 then
                        break;
                    fi;
                fi;
            fi;
        od;
        if IsEmpty(imglist[i]) then
            Info(InfoMorph, 1, "ConjugatingMat: no suitable image ccl found, reps cannot be conjugate");
            return fail;
        fi;
    od;

    gens := List(mingens, g -> ImageElm(repG.rep, g));

    dirr := Length(mingens) + 1;

    moduleG := List([1..Length(mingens)], d -> GModuleByMats(gens{[1..d]}, GF(q)));
    repeat
        dirr := dirr - 1;
    until dirr = 0 or not MTX.IsIrreducible(moduleG[dirr]);

    dirr := dirr + 1;

    if dirr > Length(mingens) then
        Error("repG must be irreducible");
    fi;

    sizes := List([1..Length(mingens)], d -> Size(Group(mingens{[1..d]})));

    D := DirectProduct(G, H);
    homG := Embedding(D, 1);
    homH := Embedding(D, 2);
    proj := Projection(D, 2);

    gens := List(mingens, g -> ImageElm(homG, g));

    C := G;
    centsize := [Size(G)];
    for g in mingens do
        C := Centralizer(C, g);
        Add (centsize, Size(C));
    od;

    # do a backtrack search through the possible images
    d := 1;
    ind := [0];
    imgs := [];
    group := [];
    cent := [H];
    matrep := [];
    imgreps := [imglist[1]];
    count := 0;
    repeat
        count := count + 1;
        ind[d] := ind[d] + 1;
        if count > limit_CMIROF then
            Info(InfoMorph, 1, "ConjugatingMat: exceeded limit  ", limit_CMIROF, " attempts");
            return "too expensive";
        elif count mod 1000 = 0 then
            Info(InfoMorph, 2, "count: ", count, " trying indices: ", ind{[1..d]},
                ", lengths: ", List([1..d], t -> Length(imgreps[t])));
        else
            Info(InfoMorph, 3, "step: ", ind{[1..d]});
        fi;

        imgs[d] := imgreps[d][ind[d]];
        matrep[d] := fail;

        if d = 1 then
            group[d] := Group(gens[d]*ImageElm(homH, imgs[d]));
        else
            group[d] := ClosureGroup(group[d-1], gens[d]*ImageElm(homH, imgs[d]));
        fi;
        if d = 1 or Size(group[d]) = sizes[d] then

            # map from gens to imgs is an isomorphism

            if d >= dirr then
                i := d;
                while i > 0 and matrep[i] = fail do
                    matrep[i] := ImageElm(repH.rep, imgs[i]);
                    i := i - 1;
                od;
                moduleH := GModuleByMats(matrep{[1..d]}, GF(q));
                mat :=  MTX.IsomorphismIrred(moduleG[d], moduleH);
            else
                mat := true;
            fi;
            if mat <> fail then
                if d < Length(gens) then
                    d := d + 1;
                    ind[d] := 0;
                    cent[d] := Centralizer(cent[d-1], imgs[d-1]);
                    if Size(cent[d]) = centsize[d] then
                        orb := OrbitsDomain(cent[d], imglist[d], OnPoints);
                        Info(InfoMorph, 3, "ConjugatingMat: ", Length(orb), " orbits found");
                        orb := Filtered (orb, o -> Length(o)=centsize[d]/centsize[d+1]);
                        imgreps[d] := List (orb, o -> o[1]);
                        Info(InfoMorph, 3, "ConjugatingMat: depth ", d, ": ",
                            Length(orb), " orbits of expected length found");
                    else
                        Info(InfoMorph, 3, "ConjugatingMat: centraliser sizes don't match");
                        imgreps[d] := [];
                    fi;
                else
                    hom := GroupGeneralMappingByImagesNC(G, H,
                        List(gens, g -> PreImagesRepresentative(homG, g)), imgs);
                    if not IsGroupHomomorphism(hom) or not IsBijective(hom) then
                        Error("hom is not an isomophism");
                    fi;
                    Info(InfoMorph, 1, "ConjugatingMat: found after ", count, " attempts");
                    Info(InfoMorph, 2, "ConjugatingMat: found indices ", ind);
                    return rec(iso :=hom, mat := mat);
                fi;
            else
                Info(InfoMorph, 3, "step: ", ind{[1..d]}, " linearity fails");
            fi;
        fi;
        while d > 0 and ind[d] = Length(imgreps[d]) do
            d := d - 1;
        od;
    until d = 0;
    Info(InfoMorph, 1, "ConjugatingMat: reps cannot be conjugate,  ", count, " attempts");

    return fail;
end);

############################################################################
##
#F  ConjugatingMatIrreducibleOrFail(G, H, F)
##
##  G and H must be irreducible matrix groups over the finite field F
##
##  computes a record x such that G^x.mat = H and x.iso is an isomorphism
##  from the source of RepresentationIsomorphism(G) to the source of
##  RepresentationIsomorphism(H). If no such matrix exists, the
##  function returns fail.
##  this works like the GAP function Morphium but only looks for linear
##  isomorphisms.
##
InstallGlobalFunction(ConjugatingMatIrreducibleOrFail, function(G, H, F)

    local q, repG, repH;

    q := Size(F);
    repG := rec(rep := RepresentationIsomorphism(G));
    repG.group := Source(repG.rep);
    repG.classes := ConjugacyClasses(repG.group);

    repH := rec(rep := RepresentationIsomorphism(H));
    repH.group := Source(repH.rep);
    repH.classes := ConjugacyClasses(repH.group);

    return ConjugatingMatIrreducibleRepOrFail(repG, repH, q, infinity, infinity);
end);

############################################################################
##
#F  ConjugatingMatImprimitiveOrFail(G, H, d, F)
##
##  G and H must be irreducible matrix groups over the finite field F
##  H must be block monomial with block dimension d
##
##  computes record x with a single component x.mat such that G^x.mat = H
##  or returns fail if no such x.mat exists
##
##  The function works best if d is small. Irreducibility is only requried
##  if ConjugatingMatIrreducibleOrFail is used
##
InstallGlobalFunction(ConjugatingMatImprimitiveOrFail, function(G, H, d, F)

# H must have minimal block dimension d and must be a block monomial group for that block size

    local n, systemsG, W, hom, r, basis, orb, posbasis, act, i, blocks,
        permW, permH, sys, permG, gensG, C, Cinv, mat;

    n := DegreeOfMatrixGroup(H);
    systemsG := ImprimitivitySystems(G);

    if Minimum(List(systemsG, sys -> Length(sys.bases[1]))) <> d then
        Info(InfoIrredsol, 1, "different minimal block dimension - groups are not conjugate");
        return fail;
    fi;

    if d = n then
        if Size(G) < 100000 then
            return ConjugatingMatIrreducibleOrFail(G, H, F);
        fi;
        hom := NiceMonomorphism(GL(n, Size(F)));
        r := RepresentativeAction(ImagesSource(hom),
                ImagesSet(hom, G), ImagesSet(hom, H));
        if r <> fail then
            Info(InfoIrredsol, 1, " conjugating matrix found");
            return rec( mat := PreImagesRepresentative(hom, r));
        else
            Info(InfoIrredsol, 1, "groups are not conjugate");
            return fail;
        fi;
    else
        W := WreathProductOfMatrixGroup(GL(d, Size(F)), SymmetricGroup(n/d));
        basis := IdentityMat(n, F);
        orb := Orbit(W, basis[1], OnRight);
        posbasis := List(basis, b -> Position(orb, b));
        Assert(1, not fail in posbasis);
        permW := Group(List(GeneratorsOfGroup(W), g -> Permutation(g, orb, OnRight)));

        blocks := [];
        for i in [0,d..n-d] do
            Add(blocks, basis{[i+1..i+d]});
        od;
        act := TransitiveIdentification(Action(H, blocks, OnSubspacesByCanonicalBasis));

        permH := Group(List(GeneratorsOfGroup(H), g -> Permutation(g, orb, OnRight)));
        Assert(1, IsSubgroup(permW, permH));

        for sys in systemsG do
            if Length(sys.bases[1]) = d
                    and TransitiveIdentification(
                        Action(G, sys.bases, OnSubspacesByCanonicalBasis)) = act then
                C := Concatenation(sys.bases);
                Cinv := C^-1;
                gensG := List(GeneratorsOfGroup(G), B -> C*B*Cinv);
                permG := Group(List(gensG, g->Permutation(g, orb, OnRight)));
                Assert(1, IsSubgroup(permW, permG));
                r := RepresentativeAction(permW, permG, permH);
                if r <> fail then
                    # compute the corresponding matrix
                    mat := List(posbasis, k -> orb[k^r]);
                    r := Cinv * mat;
                    Info(InfoIrredsol, 1, " conjugating matrix found");
                    return rec(mat := r);
                fi;
            fi;
        od;
        Info(InfoIrredsol, 1, "groups are not conjugate");
        return fail;
    fi;
end);

############################################################################
##
#F  RecognitionAISMatrixGroup(G, inds, wantmat, wantgroup, wantiso)
##
##  version of RecognitionIrreducibleSolubleMatrixGroupNC which
##  only works for absolutely irreducible groups G. This version
##  allows to prescribe a set of absolutely irreducible subgroups
##  to which G is compared. This set is described as a subset <inds> of
##  IndicesAbsolutelyIrreducibleSolubleMatrixGroups(n, q), where n is the
##  degree of G and q is the order of the trace field of G. if inds is fail,
##  all groups in the IRREDSOL library are considered.
##
##  WARNING: The result may be wrong if G is not among the groups
##  described by <inds>.
##
InstallGlobalFunction(RecognitionAISMatrixGroup,
    function(G, inds, wantmat, wantgroup, wantiso)

        local allinds, newinds, n, q, order, info, fppos, fpinfo, elminds, pos, t, tinv,
            F, H, systems, rep, i, x;

        F := TraceField(G);
        n := DegreeOfMatrixGroup(G);
        q := Size(F);
        order := Size(G);

        info := rec(); # set up the answer

        allinds := inds = fail;

        inds := SelectionIrreducibleSolubleMatrixGroups(
            n, q, 1, inds, [order], fail, fail);

        Info(InfoIrredsol, 1, Length(inds),
                    " groups of order ", order, " to compare");

        if Length(inds) = 0 then
            Error("panic: no group of order ", order, " in the IRREDSOL library");
        elif Length(inds) > 1 then
            # cut down candidate grops by looking at fingerprints
            if not TryLoadAbsolutelyIrreducibleSolubleGroupFingerprintIndex(n, q) then
                fppos := fail;
            else
                fppos := PositionSet(IRREDSOL_DATA.FP_INDEX[n][q][1], order);

                if fppos <> fail then # fingerprint info available
                    LoadAbsolutelyIrreducibleSolubleGroupFingerprintData(
                        n, q, IRREDSOL_DATA.FP_INDEX[n][q][2][fppos]);

                    fpinfo := IRREDSOL_DATA.FP[n][q][fppos];        # fingerprint info

                    if Length(fpinfo)=2 then # new format using FingerprintDerivedSubgroup
                        if IsBound(fpinfo[1]) then
                            pos := PositionSorted(fpinfo[1],
                                FingerprintDerivedSeries(Source(RepresentationIsomorphism(G))));
                            if pos = fail then
                                Error("panic: FingerprintDerivedSeries not in database");
                            fi;
                        else
                            pos := 1;
                        fi;
                        fpinfo := fpinfo[2][pos];
                        if IsInt(fpinfo) then
                            fpinfo := [[], [[]], [fpinfo]];
                        fi;
                    fi;

                    if Length(fpinfo) <> 3 then
                        Error("panic: error in fingerprint database");
                    fi;

                    Info(InfoIrredsol, 1, "computing fingerprint of group");

                    # which distingushing elements are in G?

                    if IsEmpty(fpinfo[1]) then
                        Info(InfoIrredsol, 1, "all groups have the same fingerprint");
                        # this is enough, same code handles this special case
                        elminds := [];
                    else
                        Info(InfoIrredsol, 1, "computing fingerprint of group");
                        elminds := Filtered([1..Length(fpinfo[1])], i ->
                            fpinfo[1][i] in FingerprintMatrixGroup(G));
                    fi;

                    # find relevant info in database

                    pos := PositionSet(fpinfo[2], elminds);

                    if pos = fail then
                        Error("panic: FingerprintMatrixGroup not found in database");
                    fi;

                    newinds := fpinfo[3][pos];
                    if IsInt(newinds) then
                        newinds := [newinds];
                    fi;
                    if allinds then
                        if not IsSubset(inds, newinds) then
                            Error("panic: fingerprint indices do not match the IRREDSOL library indices");
                        fi;
                        inds := newinds;
                    else
                        inds := Intersection(inds, newinds);
                    fi;

                    Info(InfoIrredsol, 1, Length(inds),
                        " groups in the library have the same fingerprint");
                fi;
            fi;
        fi;

        if Length(inds) > 1 or wantmat or wantiso then
            # rewrite G over smaller field if necc.

            Info(InfoIrredsol, 1, "rewriting group over its trace field");

            t := ConjugatingMatTraceField(G);
            if t <> One(G) then
                tinv := t^-1;
                H := GroupWithGenerators(List(GeneratorsOfGroup(G), g -> tinv * g * t));
                Assert(1, FieldOfMatrixGroup(H) = F);
                SetFieldOfMatrixGroup(H, F);
                SetTraceField(H, F);
                rep := RepresentationIsomorphism(G);
                SetRepresentationIsomorphism(H,
                        GroupHomomorphismByFunction(Source(rep), H,
                            g -> tinv * ImageElm(rep, g)*t, h -> PreImagesRepresentative(rep, t*h*tinv)));
                G := H;

                # we need the imprimitivity systems of G later anyway, so we can rule out groups
                # with different minimal block dimensions as well

                if Length(inds) > 1 then
                    Info(InfoIrredsol, 1, "computing imprimitivity systems of group");
                    systems := ImprimitivitySystems(G);
                    inds := SelectionIrreducibleSolubleMatrixGroups(
                        DegreeOfMatrixGroup(G), Size(F), 1, inds, [Order(G)],
                        [MinimalBlockDimensionOfMatrixGroup(G)], fail);
                fi;
                Info(InfoIrredsol, 1, Length(inds),
                    " groups also have the same minimal block dimension");
            fi;

            # find possible groups in the library

            for i in [1..Length(inds)-1] do
                H := IrreducibleSolubleMatrixGroup(DegreeOfMatrixGroup(G), Size(F), 1, inds[i]);

                if fppos = fail then # compare fingerprints now
                    Info(InfoIrredsol, 1, "comparing fingerprints");
                    if FingerprintMatrixGroup(G) <> FingerprintMatrixGroup(H) then
                        Info(InfoIrredsol, 1, "fingerprint different from id ",
                            IdIrreducibleSolubleMatrixGroup(H));
                        continue;
                    fi;
                fi;

                if wantiso then
                     x := ConjugatingMatIrreducibleOrFail(G, H, F);
                else
                     x := ConjugatingMatImprimitiveOrFail(G, H,
                          MinimalBlockDimensionOfMatrixGroup(G), F);
                fi;
                if x = fail then
                    Info(InfoIrredsol, 1, "group does not have id ", IdIrreducibleSolubleMatrixGroup(H));
                else
                    Assert(1, G^(x.mat) = H);
                    info.id := [DegreeOfMatrixGroup(G), Size(F), 1, inds[i]];
                    if wantgroup then
                        info.group := H;
                    fi;
                    if wantmat then
                        info.mat := t*x.mat;
                    fi;
                    if wantiso then
                        info.iso := x.iso;
                    fi;
                  Info(InfoIrredsol, 1, "group id is ", info.id);
                    return info;
                fi;
            od;
        fi;

        # we are down to the last group - this must be it

        i := inds[Length(inds)];
        info.id := [DegreeOfMatrixGroup(G), Size(F), 1, i];
        Info(InfoIrredsol, 1, "group id is ", info.id);
        if not wantgroup and not wantmat then
            return info;
        fi;
        H := IrreducibleSolubleMatrixGroup(DegreeOfMatrixGroup(G), Size(F), 1, i);
        if wantgroup then
            info.group := H;
        fi;
        if wantmat or wantiso then
            if wantiso then
                x := ConjugatingMatIrreducibleOrFail(G, H, F);
            else
                x := ConjugatingMatImprimitiveOrFail(G, H,
                          MinimalBlockDimensionOfMatrixGroup(G), F);
            fi;
            if x = fail then
                Error("panic: group not found in database");
            fi;
            if wantmat then
                info.mat := t*x.mat;
            fi;
            if wantiso then
                info.iso := x.iso;
            fi;
        fi;
        return info;

    end);

############################################################################
##
#F  RecognitionIrreducibleSolubleMatrixGroup(G, wantmat, wantgroup)
##
##  Let G be an irreducible soluble matrix group over a finite field.
##  This function identifies a conjugate H of G group in the library.
##
##  It returns a record which has the following entries:
##  id:                     contains the id of H (and thus of G),
##                            cf. IdIrreducibleSolubleMatrixGroup
##  mat: (optional)     a matrix x such that G^x = H
##  group: (optional)  the group H
##
##  The entries mat and group are only present if the booleans wantmat and/or
##  wantgroup are true, respectively.
##
##  Currently, wantmat may only be true if G is absolutely irreducible.
##
##  Note that in most cases, the function will be much slower if wantmat
##  is set to true.
##
InstallGlobalFunction(RecognitionIrreducibleSolubleMatrixGroup,
    function(arg)

        local G, wantmat, wantgroup, wantiso, info;

        if Length(arg) < 2 or Length(arg) > 4 then
          Error("RecognitionIrreducibleSolubleMatrixGroup(G [, wantmat [, wantgroup [,wantiso]]])");
        fi;

        # test if G is soluble

        G := arg[1];
        if not IsMatrixGroup(G) or not IsFinite (FieldOfMatrixGroup(G))
                or not IsSolvableGroup(G) then
            Error("G must be a soluble matrix group over a finite field");
        fi;

        wantmat := arg[2];
        if Length(arg) = 2 then
            wantgroup := false;
        else
            wantgroup := arg[3];
            if Length(arg) = 3 then
                wantiso := false;
            else
                wantiso := arg[4];
            fi;
        fi;
        if not IsBool (wantmat) or not IsBool (wantgroup) or not IsBool (wantiso) then
            Error("wantmat, wantgroup and wantiso must be booleans");
        fi;
        info := RecognitionIrreducibleSolubleMatrixGroupNC(G,
            wantmat, wantgroup, wantiso);
        if info = fail then
            Error("This group is not within the scope of the IRREDSOL library");
        fi;
        return info;
    end);

############################################################################
##
#F  RecognitionIrreducibleSolubleMatrixGroupNC(G, wantmat, wantgroup, wantiso)
##
##  version of RecognitionIrreducibleSolubleMatrixGroup which does not check
##  its arguments and returns fail if G is not within the scope of the
##  IRREDSOL library
##
InstallGlobalFunction(RecognitionIrreducibleSolubleMatrixGroupNC,
    function(G, wantmat, wantgroup, wantiso)

        local moduleG, module, info, newinfo, conjinfo, gens, perm_pow, basis,
            gensG, repG, repH, H, d, e, q, gal;

        # reduce to the absolutely irreducible case

        repG := RepresentationIsomorphism (G);
        gensG := List(GeneratorsOfGroup(Source(repG)), x -> ImageElm(repG, x));
        moduleG := GModuleByMats (gensG, FieldOfMatrixGroup(G));

        if not MTX.IsIrreducible (moduleG) then
            Error("G must be irreducible over FieldOfMatrixGroup(G)");
        elif MTX.IsAbsolutelyIrreducible (moduleG) then
            Info(InfoIrredsol, 1, "group is absolutely irreducible");
            info := RecognitionAISMatrixGroup(G, fail, wantmat, wantgroup, wantiso);
            if info = fail then
                return fail;
            else
                newinfo := rec(id := [DegreeOfMatrixGroup(G), Size(TraceField(G)), 1, info.id[4]]);
                if wantgroup then
                    newinfo.group := info.group;
                fi;
                if wantmat then
                    newinfo.mat := info.mat;
                fi;
                if wantiso then
                    newinfo.iso := info.iso;
                fi;
             fi;
        else
            Info(InfoIrredsol, 1, "reducing to the absolutely irreducible case");
            module := GModuleByMats (gensG, GF(CharacteristicOfField (G)^MTX.DegreeSplittingField (moduleG)));
            repeat
                basis := MTX.ProperSubmoduleBasis (module);
                module := MTX.InducedActionSubmodule (module, basis);
            until MTX.IsIrreducible (module);
            Assert(1, MTX.IsAbsolutelyIrreducible (module));

            # construct absolutely irreducible group

            H := Group(MTX.Generators (module));
            SetIsAbsolutelyIrreducibleMatrixGroup(H, true);
            SetIsIrreducibleMatrixGroup(H, true);
            SetIsSolvableGroup(H, true);
            SetSize(H, Size(G));
            e := LogInt(Size(TraceField (H)), CharacteristicOfField (H));
            d := DegreeOfMatrixGroup(G)/MTX.Dimension (module);
            Assert(1, e mod d = 0);

            # construct representation isomorphism for H

            repH := GroupGeneralMappingByImagesNC (Source (repG), H,
                    GeneratorsOfGroup(Source(repG)),
                    MTX.Generators (module));
            SetIsGroupHomomorphism (repH, true);
            SetIsBijective (repH, true);
            SetRepresentationIsomorphism (H, repH);

            # recognize absolutely irreducible component

            info := RecognitionAISMatrixGroup(H, fail, wantmat, false, wantiso);
            if info = fail then
                return fail;
            fi;

            # translate results back

            q := CharacteristicOfField(H)^(e/d);
            SetTraceField (G, GF(q));
            Assert(1, q^d = info.id[2]);
            perm_pow := PermCanonicalIndexIrreducibleSolubleMatrixGroup(
                    DegreeOfMatrixGroup(G), q, d, info.id[4]);

            newinfo := rec(
                id := [DegreeOfMatrixGroup(G), q, d, info.id[4]^(perm_pow.perm^perm_pow.pow)]);
            if wantgroup then

                newinfo.group := CallFuncList(IrreducibleSolubleMatrixGroup, newinfo.id);
            fi;

            if wantmat then
                Info(InfoIrredsol, 1, "determining conjugating matrix");
                # raising to gal-th power is the field automorphism which maps H to a conjugate of
                # the absolutely irreducible group which is used to construct the irreducible group
                # we want to find
                gal := q^perm_pow.pow;
                if gal = 1 then
                     gens := List(MTX.Generators(module), mat -> mat^info.mat);
                     Info(InfoIrredsol, 1, "already got right Galois conjugate");
                     if wantiso then
                        if Source (RepresentationIsomorphism (newinfo.group)) <> Range (info.iso) then
                            Error("sources of isomorphisms don't match");
                        fi;
                        newinfo.iso := info.iso;
                    fi;
               else
                    # construct Galois conjugate of module

                    Info(InfoIrredsol, 1, "computing and recognizing Galois conjugate");

                    module := GModuleByMats (
                        List(MTX.Generators (module), mat -> List(mat, row -> List(row, x -> x^gal))),
                        MTX.Dimension (module), MTX.Field(module));

                    H := Group(MTX.Generators (module));
                    SetIsAbsolutelyIrreducibleMatrixGroup(H, true);
                    SetIsIrreducibleMatrixGroup(H, true);
                    SetIsSolvableGroup(H, true);
                    SetSize(H, Size(G));

                    # construct representation isomorphism for H

                    repH := GroupGeneralMappingByImagesNC (Source (repG), H,
                            GeneratorsOfGroup(Source(repG)),
                            MTX.Generators (module));
                    SetIsGroupHomomorphism (repH, true);
                    SetIsBijective (repH, true);
                    SetRepresentationIsomorphism (H, repH);

                    # recognize absolutely irreducible component

                    conjinfo := RecognitionAISMatrixGroup(H, [perm_pow.min], true, false, wantiso);
                    if conjinfo = fail then
                        Error("panic: internal error, Galois conjugate isn't in the library");
                    elif conjinfo.id <> [info.id[1], info.id[2], 1, perm_pow.min] then
                        Error("panic: internal error, didn't find correct Galois conjugate");
                    fi;
                    gens := List(MTX.Generators (module), mat -> mat^conjinfo.mat);
                    if wantiso then
                        if Source (RepresentationIsomorphism (newinfo.group)) <> Range (conjinfo.iso) then
                            Error("sources of isomorphisms don't match");
                        fi;
                        newinfo.iso := conjinfo.iso;
                    fi;
                fi;

                basis := CanonicalBasis (AsVectorSpace (GF(q), GF(q^d)));
                      # it is important to use CanonicalBasis here, in order to be sure
                      # that the result is the same as during the construction of
                      # the corresponding irreducible group

                # now construct a module over GF(q)
                 module := GModuleByMats (
                     List(gens, mat -> BlownUpMat (basis, mat)),
                     MTX.Dimension (moduleG),
                     MTX.Field (moduleG));

                 newinfo.mat := MTX.Isomorphism (moduleG, module);
                    if newinfo.mat = fail then
                        Error("panic: no conjugating mat found");
                    fi;
            fi;

        fi;
        Info(InfoIrredsol, 1, "irreducible group id is", newinfo.id);
        return newinfo;
    end);

############################################################################
##
#M  IdIrreducibleSolubleMatrixGroup(<G>)
##
##  see the IRREDSOL manual
##
InstallMethod(IdIrreducibleSolubleMatrixGroup,
    "for irreducible soluble matrix group over finite field", true,
    [IsFFEMatrixGroup
        and IsIrreducibleMatrixGroup and IsSolvableGroup], 0,

    function(G)

        local info;

        info := RecognitionIrreducibleSolubleMatrixGroupNC(G, false, false, false);
        if info = fail then
            Error("group is beyond the scope of the IRREDSOL library");
        else
            return info.id;
        fi;
    end);

RedispatchOnCondition(IdIrreducibleSolubleMatrixGroup, true,
    [IsFFEMatrixGroup],
    [IsIrreducibleMatrixGroup and IsSolvableGroup], 0);

############################################################################
##
#V  IRREDSOL_DATA.MS_GROUP_INDEX
##
##  translation table for Mark Short's irredsol library
##
IRREDSOL_DATA.MS_GROUP_INDEX :=
[ ,
  [ , [ [ 2, 1 ], [ 1, 1 ] ], [ [ 2, 1 ], [ 1, 1 ], [ 1, 5 ], [ 2, 2 ], [ 1,
                  4 ], [ 1, 3 ], [ 1, 2 ] ],,
        [ [ 2, 1 ], [ 1, 11 ], [ 2, 2 ], [ 1, 5 ], [ 1, 4 ], [ 2, 3 ],
             [ 1, 8 ], [ 1, 9 ], [ 2, 4 ], [ 1, 3 ], [ 1, 2 ], [ 1, 10 ],
             [ 1, 7 ], [ 2, 5 ], [ 1, 14 ], [ 1, 1 ], [ 1, 6 ], [ 1, 13 ],
             [ 1, 12 ] ],,
        [ [ 2, 1 ], [ 1, 7 ], [ 1, 10 ], [ 1, 18 ], [ 2, 2 ], [ 1, 8 ],
             [ 1, 6 ], [ 2, 3 ], [ 1, 14 ], [ 2, 4 ], [ 1, 16 ], [ 1, 4 ],
             [ 1, 9 ], [ 1, 5 ], [ 2, 5 ], [ 1, 17 ], [ 1, 21 ], [ 1, 22 ],
             [ 1, 13 ], [ 1, 3 ], [ 1, 2 ], [ 1, 15 ], [ 2, 6 ], [ 1, 12 ],
             [ 1, 23 ], [ 1, 1 ], [ 1, 20 ], [ 1, 11 ], [ 1, 19 ] ],,,,
        [ [ 2, 1 ], [ 2, 2 ], [ 2, 3 ], [ 1, 21 ], [ 1, 10 ], [ 2, 4 ],
             [ 1, 26 ], [ 1, 6 ], [ 2, 5 ], [ 1, 20 ], [ 1, 17 ], [ 2, 6 ],
             [ 1, 25 ], [ 1, 5 ], [ 1, 7 ], [ 2, 7 ], [ 1, 14 ], [ 2, 8 ],
             [ 1, 16 ], [ 1, 29 ], [ 2, 9 ], [ 1, 22 ], [ 1, 9 ], [ 1, 8 ],
             [ 1, 24 ], [ 2, 10 ], [ 1, 13 ], [ 1, 30 ], [ 1, 4 ], [ 1, 18 ],
             [ 1, 19 ], [ 2, 11 ], [ 1, 23 ], [ 1, 2 ], [ 1, 3 ], [ 1, 12 ],
             [ 2, 12 ], [ 1, 15 ], [ 1, 28 ], [ 1, 1 ], [ 1, 11 ], [ 1, 27 ] ],,
        [ [ 1, 26 ], [ 2, 1 ], [ 1, 21 ], [ 1, 23 ], [ 2, 2 ], [ 1, 25 ],
             [ 1, 27 ], [ 2, 3 ], [ 1, 43 ], [ 1, 18 ], [ 1, 29 ], [ 1, 11 ],
             [ 2, 4 ], [ 1, 14 ], [ 1, 20 ], [ 1, 22 ], [ 1, 13 ], [ 1, 19 ],
             [ 1, 30 ], [ 2, 5 ], [ 1, 51 ], [ 1, 50 ], [ 2, 6 ], [ 1, 40 ],
             [ 1, 38 ], [ 1, 32 ], [ 1, 8 ], [ 1, 9 ], [ 2, 7 ], [ 1, 44 ],
             [ 1, 17 ], [ 1, 28 ], [ 1, 15 ], [ 1, 12 ], [ 1, 49 ], [ 1, 48 ],
             [ 2, 8 ], [ 1, 36 ], [ 1, 42 ], [ 1, 5 ], [ 1, 10 ], [ 1, 7 ],
             [ 1, 6 ], [ 1, 47 ], [ 1, 39 ], [ 1, 35 ], [ 2, 9 ], [ 1, 31 ],
             [ 1, 16 ], [ 1, 52 ], [ 1, 37 ], [ 1, 3 ], [ 1, 2 ], [ 1, 46 ],
             [ 2, 10 ], [ 1, 34 ], [ 1, 41 ], [ 1, 1 ], [ 1, 45 ], [ 1, 33 ],
             [ 1, 24 ], [ 1, 4 ] ] ],
  [ , [ [ 3, 1 ], [ 1, 1 ] ], [ [ 1, 5 ], [ 3, 1 ], [ 1, 4 ], [ 1, 3 ],
             [ 1, 2 ], [ 3, 2 ], [ 1, 7 ], [ 1, 1 ], [ 1, 6 ] ],,
        [ [ 1, 12 ], [ 1, 15 ], [ 1, 16 ], [ 1, 6 ], [ 3, 1 ], [ 1, 3 ],
             [ 1, 10 ], [ 1, 11 ], [ 1, 9 ], [ 3, 2 ], [ 1, 19 ], [ 1, 4 ],
             [ 1, 5 ], [ 1, 14 ], [ 1, 13 ], [ 3, 3 ], [ 1, 18 ], [ 1, 8 ],
             [ 1, 2 ], [ 1, 7 ], [ 1, 17 ], [ 1, 1 ] ] ],
  [ , [ [ 4, 1 ], [ 2, 3 ], [ 4, 2 ], [ 2, 1 ], [ 1, 5 ], [ 2, 2 ], [ 1, 2 ],
             [ 1, 3 ], [ 1, 4 ], [ 1, 1 ] ],
        [ [ 4, 1 ], [ 2, 20 ], [ 4, 2 ], [ 2, 11 ], [ 2, 9 ], [ 2, 8 ],
             [ 4, 3 ], [ 2, 12 ], [ 2, 16 ], [ 2, 17 ], [ 4, 4 ], [ 1, 73 ],
             [ 1, 11 ], [ 1, 10 ], [ 2, 4 ], [ 1, 9 ], [ 1, 12 ], [ 1, 34 ],
             [ 1, 32 ], [ 2, 7 ], [ 2, 6 ], [ 1, 33 ], [ 2, 5 ], [ 2, 10 ],
             [ 4, 5 ], [ 1, 71 ], [ 2, 18 ], [ 2, 15 ], [ 1, 72 ], [ 2, 24 ],
             [ 2, 23 ], [ 1, 13 ], [ 1, 14 ], [ 1, 7 ], [ 1, 6 ], [ 2, 3 ],
             [ 1, 36 ], [ 1, 41 ], [ 2, 2 ], [ 1, 27 ], [ 1, 42 ], [ 1, 26 ],
             [ 1, 35 ], [ 2, 19 ], [ 2, 14 ], [ 1, 69 ], [ 4, 6 ], [ 1, 70 ],
             [ 1, 5 ], [ 1, 49 ], [ 1, 38 ], [ 1, 37 ], [ 2, 22 ], [ 2, 25 ],
             [ 1, 57 ], [ 1, 65 ], [ 2, 26 ], [ 1, 8 ], [ 1, 28 ], [ 1, 39 ],
             [ 1, 30 ], [ 1, 40 ], [ 1, 29 ], [ 1, 44 ], [ 1, 43 ], [ 1, 21 ],
             [ 2, 1 ], [ 1, 67 ], [ 1, 68 ], [ 2, 13 ], [ 1, 76 ], [ 1, 3 ],
             [ 1, 4 ], [ 1, 2 ], [ 1, 31 ], [ 1, 59 ], [ 1, 63 ], [ 1, 64 ],
             [ 1, 62 ], [ 2, 21 ], [ 1, 61 ], [ 1, 19 ], [ 1, 45 ], [ 1, 24 ],
             [ 1, 48 ], [ 1, 46 ], [ 1, 47 ], [ 1, 58 ], [ 1, 66 ], [ 1, 75 ],
             [ 1, 1 ], [ 1, 22 ], [ 1, 23 ], [ 1, 60 ], [ 1, 25 ], [ 1, 54 ],
             [ 1, 55 ], [ 1, 56 ], [ 1, 74 ], [ 1, 20 ], [ 1, 18 ], [ 1, 52 ],
             [ 1, 51 ], [ 1, 53 ], [ 1, 17 ], [ 1, 16 ], [ 1, 50 ], [ 1, 15 ] ] ]
     ,
  [ , [ [ 5, 1 ], [ 1, 1 ] ], [ [ 5, 1 ], [ 5, 2 ], [ 1, 11 ], [ 1, 4 ], [ 1,
                  12 ], [ 5, 3 ], [ 1, 3 ], [ 1, 5 ], [ 1, 6 ], [ 5, 4 ],
             [ 1, 7 ], [ 1, 2 ], [ 1, 8 ], [ 1, 10 ], [ 1, 1 ], [ 1, 9 ] ] ],
  [ , [ [ 6, 1 ], [ 3, 2 ], [ 3, 4 ], [ 6, 2 ], [ 2, 4 ], [ 2, 3 ],
             [ 1, 15 ], [ 3, 5 ], [ 2, 5 ], [ 1, 9 ], [ 1, 10 ], [ 6, 3 ],
             [ 2, 7 ], [ 2, 8 ], [ 2, 2 ], [ 3, 1 ], [ 1, 11 ], [ 2, 11 ],
             [ 1, 16 ], [ 3, 3 ], [ 2, 1 ], [ 1, 6 ], [ 1, 7 ], [ 2, 6 ],
             [ 2, 12 ], [ 1, 21 ], [ 1, 20 ], [ 1, 14 ], [ 1, 13 ], [ 1, 8 ],
             [ 1, 5 ], [ 1, 17 ], [ 1, 19 ], [ 1, 3 ], [ 1, 4 ], [ 1, 2 ],
             [ 2, 10 ], [ 1, 12 ], [ 1, 1 ], [ 1, 18 ] ] ],
  [ , [ [ 7, 1 ], [ 1, 1 ] ] ] ];

MakeImmutable(IRREDSOL_DATA.MS_GROUP_INDEX);

############################################################################
##
#F  IdIrreducibleSolubleMatrixGroupIndexMS(<n>, <p>, <k>)
##
##  see the IRREDSOL manual
##
InstallGlobalFunction(IdIrreducibleSolubleMatrixGroupIndexMS,
    function(n, p, k)
        if IsInt(n) and n > 1 and IsPosInt(p) and IsPrimeInt(p)
                and IsPosInt(k) then
            if IsBound(IRREDSOL_DATA.MS_GROUP_INDEX[n]) then
                if IsBound(IRREDSOL_DATA.MS_GROUP_INDEX[n][p]) then
                    if IsBound(IRREDSOL_DATA.MS_GROUP_INDEX[n][p][k]) then
                        return Concatenation([n, p], IRREDSOL_DATA.MS_GROUP_INDEX[n][p][k]);
                    else
                        Error("k is out of range");
                    fi;
                else
                    Error("p is out of range");
                fi;
            else
                Error("n is out of range");
            fi;
        else
            Error("n, p, k must be integers, n > 1, and p must be a prime");
        fi;
    end);

############################################################################
##
#F  IndexMSIdIrreducibleSolubleMatrixGroup(<n>, <q>, <d>, <k>)
##
##  see the IRREDSOL manual
##
InstallGlobalFunction(IndexMSIdIrreducibleSolubleMatrixGroup,
    function(n, p, d, k)
        local pos;

        if IsInt(n) and n > 1 and IsPosInt(p) and IsPrimeInt(p)
                and IsPosInt(k) and IsPosInt(d) and n mod d = 0 then
            if IsBound(IRREDSOL_DATA.MS_GROUP_INDEX[n]) then
                if IsBound(IRREDSOL_DATA.MS_GROUP_INDEX[n][p]) then
                    pos := Position (IRREDSOL_DATA.MS_GROUP_INDEX[n][p], [d, k]);
                    if pos <> fail then
                        return [n, p, pos];
                    else
                        Error("inadmissible value for k");
                    fi;
                else
                    Error("p is out of range");
                fi;
            else
                Error("n is out of range");
            fi;
        else
            Error("n, p, d, k must be integers, n > 1, p must be a prime, and d must divide n");
        fi;
    end);

############################################################################
##
#E
##