)abbrev package MAMA MatrixManipulation
++ Author: Raoul Bourquin
++ Date Created: 17 November 2012
++ Description:
++ Some functions for manipulating (dense) matrices.
++ Supported are various kinds of slicing, splitting
++ and stacking of matrices. The functions resemble
++ operations often used in numerical linear algebra
++ algorithms.
MatrixManipulation(R, Row, Col, M) : Exports == Implementation where
  R : Type
  Row : FiniteLinearAggregate R
  Col : FiniteLinearAggregate R
  M : TwoDimensionalArrayCategory(R, Row, Col)

  I ==> Integer
  PI ==> PositiveInteger
  NNI ==> NonNegativeInteger
  LI ==> List I
  SI ==> Segment I
  LNNI ==> List NNI


  Exports ==> with

    -- Slicing matrices

    element : (M, I, I) -> M
      ++ \spad{element} returns a single element out of a matrix.
      ++ The element is put into a one by one matrix.

    rowMatrix : (M, I) -> M
      ++ \spad{rowMatrix} returns a single row out of a matrix.
      ++ The row is put into a one by N matrix.

    rows : (M, LI) -> M
      ++ \spad{rows} returns several rows out of a matrix.
      ++ The rows are stacked into a matrix.

    rows : (M, SI) -> M
      ++ \spad{rows} returns several rows out of a matrix.
      ++ The rows are stacked into a matrix.

    columnMatrix : (M, I) -> M
      ++ \spad{columnMatrix} returns a single column out of a matrix.
      ++ The column is put into a one by N matrix.

    columns : (M, LI) -> M
      ++ \spad{columns} returns several columns out of a matrix.
      ++ The columns are stacked into a matrix.

    columns : (M, SI) -> M
      ++ \spad{columns} returns several columns out of a matrix.
      ++ The columns are stacked into a matrix.

    subMatrix : (M, LI, LI) -> M
      ++ \spad{subMatrix} returns several elements out of a matrix.
      ++ The elements are stacked into a submatrix.

    subMatrix : (M, SI, SI) -> M
      ++ \spad{subMatrix} returns several elements out of a matrix.
      ++ The elements are stacked into a submatrix.

    if R has AbelianMonoid then
      diagonalMatrix : (M, I) -> M
        ++ \spad{diagonalMatrix} returns a diagonal out of a matrix.
        ++ The diagonal is put into a matrix of same shape as the
        ++ original one. Positive integer arguments select upper
        ++ off-diagonals, negative ones lower off-diagonals.
      diagonalMatrix : M -> M
        ++ \spad{diagonalMatrix} returns the main diagonal out of
        ++ a matrix. The diagonal is put into a matrix of same shape
        ++ as the original one.

      bandMatrix : (M, LI) -> M
        ++ \spad{bandMatrix} returns multiple diagonals out of a matrix.
        ++ The diagonals are put into a matrix of same shape as the
        ++ original one. Positive integer arguments select upper
        ++ off-diagonals, negative ones lower off-diagonals.

      bandMatrix : (M, SI) -> M
        ++ \spad{bandMatrix} returns multiple diagonals out of a matrix.
        ++ The diagonals are put into a matrix of same shape as the
        ++ original one. Positive integer arguments select upper
        ++ off-diagonals, negative ones lower off-diagonals.

    -- Splitting matrices

    blockSplit : (M, LNNI, PI) -> List List M
      ++ \spad{blockSplit} splits a matrix into multiple
      ++ submatrices row and column wise, dividing
      ++ a matrix into blocks.

    blockSplit : (M, PI, LNNI) -> List List M
      ++ \spad{blockSplit} splits a matrix into multiple
      ++ submatrices row and column wise, dividing
      ++ a matrix into blocks.

  Implementation ==> add

    minr ==> minRowIndex
    maxr ==> maxRowIndex
    minc ==> minColIndex
    maxc ==> maxColIndex

    element(A, r, c) ==
        subMatrix(A, r, r, c, c)


    rowMatrix(A : M, r : I) : M ==
        subMatrix(A, r, r, minc A, maxc A)


    rows(A : M, lst : LI) : M ==
        nc := ncols(A)
        nc = 0 => qnew(#lst, nc)$M
        ls := expand((minc A .. maxc A)@SI)
        subMatrix(A, lst, ls)


    rows(A : M, si : SI) : M ==
        rows(A, expand(si))


    columnMatrix(A : M, c : I) : M ==
        subMatrix(A, minr A, maxr A, c, c)


    columns(A:M, lst : LI) : M ==
        nr := nrows(A)
        nr = 0 => qnew(nr, #lst)$M
        ls := expand((minr A .. maxr A)@SI)
        subMatrix(A, ls, lst)


    columns(A : M, si : SI) : M ==
        columns(A, expand(si))


    fill_diagonal(B : M, A : M, nr : NNI, nc : NNI, n : I) : Void ==
      n > (nc-1) => error "requested diagonal out of range"
      n < 0 and abs(n) > (nr-1) => error "requested diagonal out of range"

      if n >= 0 then
        dl := min(nc-n, nr)
        sr := minr(A)
        sc := minc(A) + n
      else
        dl := min(nc, nr-abs(n))
        sr := minr(A) + abs(n)
        sc := minc(A)

      for i in 0..(dl-1) repeat
        qsetelt!(B, sr+i, sc+i, A(sr+i, sc+i))


    if R has AbelianMonoid then
      diagonalMatrix(A, n) ==
        nr := nrows(A)
        nc := ncols(A)
        B := new(nr, nc, 0$R)
        fill_diagonal(B, A, nr, nc, n)
        B


      diagonalMatrix(A) ==
          diagonalMatrix(A, 0)


      bandMatrix(A:M, ln:LI) : M ==
        nr := nrows(A)
        nc := ncols(A)
        B := new(nr, nc, 0$R)
        for n in ln repeat
            fill_diagonal(B, A, nr, nc, n)
        B


      bandMatrix(A:M, si:SI) : M ==
        bandMatrix(A, expand(si))


    subMatrix(A:M, lr:LI, lc:LI) : M ==
        n := #lr
        m := #lc
        minR := minr A
        minC := minc A
        res := qnew(n, m)$M
        for i in 1..n for ii in lr repeat
            for j in 1..m for jj in lc repeat
                qsetelt!(res, minR-1+i, minC-1+j, qelt(A, ii, jj))
        res


    subMatrix(A:M, sr:SI, sc:SI) : M ==
      subMatrix(A, low sr, high sr, low sc, high sc)


    -- Stack matrices


    blockSplit(A:M, lr:LNNI, nc:PI) : List List M ==
      --map( (X:M):(List M) +-> horizSplit(X, nc), vertSplit(A, lr) )$ListFunctions2(M, List M)
      [ horizSplit(X, nc) for X in vertSplit(A, lr) ]


    blockSplit(A:M, nr:PI, lc:LNNI) : List List M ==
      --map( (X:M):(List M) +-> horizSplit(X, lc), vertSplit(A, nr) )$ListFunctions2(M, List M)
      [ horizSplit(X, lc) for X in vertSplit(A, nr) ]
