SPL

Matrices and Symbolic Matrices

SPL Objects

s1 := I(4);                     # Identity matrix
s2 := F(2);                     # Butterfly matrix
s3 := L(8, 2);                  # Stride permutation matrix
s4 := Mat([[1,2],[3,4]]);       # GAP Matrix as SPL object
RowVec(4); ColVec(4);           # row and column vectors
O(4); O(3, 4);                  # zero matrix, square and rectangular

SPL Operations

s2 * s4; Compose(s2, s4);       # product of SPL
DirectSum(s1, s2);              # matrix direct sum
Tensor(s1, s2);         # Kronecker product of matrices
HStack(s2, s4);         # [[s2,s4]]
VStack(s2, s4);         # [[s2],[s4]]

Operations for SPL Objects

pm(s1);                         # print as matrix
MatSPL(s2);                     # convert to GAP matrix
s3.transpose();         # symbolic transposition
# List all SPL objects
List(Filtered(Dir(spiral.spl), o->IsSPL(spiral.spl.(o))),
         e->spiral.spl.(e));

Still SPL, But Towards Σ-SPL

Permutations

s1 := L(8,4);           # The stride permutation...
MatSPL(s1);             # ...is a matrix...
s1.lambda();            # ...and a symbolic function
L(8, 4) * L(8, 2);      # == I(8)
Tensor(I(2), L(4,2)) * L(8,2);  # digit perm needs expression

# other permutation matrices/functions
J(4); Z(4,1); CRT(4,5); RR(13,3,2);
Tensor(J(4), Z(4, 1));

Diagonals

d:= Diag(fConst(4,1.1));                # Diagonal matrix
d.element;
e:= RCDiag(FList(TReal, [1..16]));      # RC(Diag(...))
e.element;
f := DiagCpxSplit(FList(TReal, [1..16]));
f.element;

# diagonal matrix with dependency on free variable
i := Ind(2);
Diag(fCompose(FList(TReal, [1..4]), fTensor(fId(2), fBase(i))));

More SPL Operators

Various

s1 := DFT(4).terminate();       # Get the complex DFT(4) matrix
s2 := RC(s1);                   # convert it to a real 8x8 matrix
s3 := COND(Ind(), I(2), F(2));  # conditional matrix
s4:= Tensor(DFT(4), I(4));              # transforms are SPL objects
MatSPL(DFT(4)) * [1..4];        # and support SPL and Matrix operations
ConjLR(Tensor(I(2), F(2)), L(4,2), L(4,2));     # classical identity

RowDirectSum(1, F(2), J(2));    # overlapped direct sum
RowTensor(5, 1, F(2));          # overlapped tensor
ColDirectSum(1, F(2), J(2));    # overlapped direct sum
ColTensor(5, 1, F(2));          # overlapped tensor

Iterative

i := Ind(4);
s5 := IterDirectSum(i, F(2));           # iterative direct sum
s6 := IterDirectSum(i, Mat([[i+1, 2*i],[-3*i, 4*i+5]]));
MatSPL(s6);

s7 := IterHStack(i, F(2));              # iterative HStack
s8 := IterVStack(i, F(2));              # iterative VStack

Defining SPL Objects

SPL Matrices

# spiral-core\namespaces\spiral\spl\symbols.gi
Class(F, Sym, rec(
        def := size -> Checked(IsPosInt(size),
        Cond(size = 1, Mat([[1]]),
                 size = 2, Mat([[1,1], [1,-1]]),
                 Mat(Global.DFT(size)))),

        isReal    := self >> self.params[1] <= 2,
        isPermutation := False,
        transpose := self >> self,
        conjTranspose := self >> self,
        inverse := self >> let(n:=self.params[1],
                Cond(n=1, self, n=2, 1/2*F(2),
                Error("Inverse not supported"))),
        toAMat    := self >> DFTAMat(self.params[1]),
        printlatex := (self) >> Print(" F_{", self.params[1], "} ")
));

Example

s := F(2);
MatSPL(s);

SPL Operator

# spiral-core\namespaces\spiral\spl\Conjugate.gi
Class(Conjugate, BaseOperation, rec(
        new := (self, spl, conj) >> Checked(IsSPL(spl), IsSPL(conj),
        When(Dimensions(conj) = [1,1], spl,
                 SPL(WithBases( self,
                         rec( _children := [spl, conj],
                         dimensions := spl.dimensions ))))),
        isPermutation := False,
        dims := self >> self.dimensions,
        toAMat := self >>
                ConjugateAMat(AMatSPL(self._children[1]),
                        AMatSPL(self._children[2])),
        print := (self, i, is) >>
                Print("(", self.child(1).print(i, is), ") ^ (",
                self.child(2).print(i+is, is), ")", self.printA()),
        transpose := self >> Inherit(self, rec(_children := [
                TransposedSPL(self._children[1]), self._children[2] ],
        dimensions := Reversed(self.dimensions))),
        arithmeticCost := (self, costMul, costAddMul) >>
                self._children[1].arithmeticCost(costMul, costAddMul)
));

SPL Permutation

# spiral-core\namespaces\spiral\spl\perms.gi
Class(L, PermClass, rec(
        def := (n,str) -> Checked(IsPosIntSym(n), IsPosIntSym(str),
                (not (IsInt(n) and IsInt(str)) or n mod str = 0), rec()),

        domain := self >> self.params[1],
        range  := self >> self.params[1],

        lambda := self >> let(
                n := self.params[1], str := self.params[2], i := Ind(n),
                Lambda(i, idiv(i, n/str) + str * imod(i, n/str))),

        transpose := self >> self.__bases__[1](self.params[1],
                self.params[1] / self.params[2]),
        isSymmetric := self >> (self.params[1] = self.params[2]^2) or
                (self.params[2] = 1) or (self.params[2] = self.params[1]),
));

Transforms, RuleTrees, and SPL Formulas

From Transform to SPL

# create the objects and lower them
t := DFT(4);
rt := RandomRuleTree(t, SpiralDefaults);
s := SPLRuleTree(rt);

Conversions Transform/SPL/GAP Matrix

t.terminate();                  # transform -> SPL
tm := MatSPL(t);                # transform -> GAP matrix
sm := MatSPL(s);                # SPL formula -> GAP matrix

Symbolic Correctness

tm = sm;                        # same, as GAP matrices
InfinityNormMat(tm - sm);       # computes the matrix norm

t1 := DFT(13);                  # for this we lose symbolic equivalency
rt1 := RandomRuleTree(t1, SpiralDefaults);
s1 := SPLRuleTree(rt1); # size 13 triggers Rader
tm1 := MatSPL(t1);              # this is fully symbolic, but
sm1 := MatSPL(s1);              # Rader requires the FFT
tm1 = sm1;                      # thus floating-point matrix
InfinityNormMat(tm1 - sm1);     # but equivalent wrt. floating-point