packages feed

goal-core-0.20: Goal/Core/Vector/Storable.hs

 {-# OPTIONS_GHC -fplugin=GHC.TypeLits.KnownNat.Solver -fplugin=GHC.TypeLits.Normalise -fconstraint-solver-iterations=10 #-}
-- | Vectors and Matrices with statically typed dimensions based on storable vectors and using HMatrix where possible.
module Goal.Core.Vector.Storable
    ( -- * Vector
      module Data.Vector.Storable.Sized
      -- ** Construction
    , doubleton
    , range
      -- ** Deconstruction
    , concat
    , breakEvery
    , toPair
    -- ** Computation
    , average
    , zipFold
    -- * Matrix
    , Matrix
    , nRows
    , nColumns
    -- ** Construction
    , fromRows
    , fromColumns
    , matrixIdentity
    , outerProduct
    , sumOuterProduct
    , averageOuterProduct
    , weightedAverageOuterProduct
    , diagonalMatrix
    , fromLowerTriangular
    -- ** Deconstruction
    , toRows
    , toColumns
    , lowerTriangular
    , takeDiagonal
    -- ** Manipulation
    , columnVector
    , rowVector
    , combineTriangles
    , diagonalConcat
    , horizontalConcat
    , verticalConcat
    -- ** Computation
    , trace
    , withMatrix
    -- *** BLAS
    , scale
    , add
    , dotProduct
    , dotMap
    , matrixVectorMultiply
    , matrixMatrixMultiply
    , matrixMap
    , eigens
    , isSemiPositiveDefinite
    , determinant
    , inverseLogDeterminant
    , inverse
    , pseudoInverse
    , matrixRoot
    , transpose
    -- *** Least Squares
    , linearLeastSquares
    , meanSquaredError
    , rSquared
    , l2Norm
    , unsafeCholesky
    -- *** Convolutions
    , crossCorrelate2d
    , convolve2d
    , kernelOuterProduct
    , kernelTranspose
    -- ** Miscellaneous
    , prettyPrintMatrix
    ) where


--- Imports ---


-- Goal --

import Goal.Core.Util hiding (average,breakEvery,range)

-- Unqualified --

import Data.Proxy
import Data.Complex
import Foreign.Storable
import Data.Vector.Storable.Sized
import Numeric.LinearAlgebra (Field,Numeric)
import GHC.TypeNats
import Prelude hiding (concat,foldr1,concatMap,replicate,(++),length,map,sum,zip,and)

-- Qualified --

import qualified Prelude
import qualified Data.Vector.Storable as S
import qualified Goal.Core.Vector.Generic as G
import qualified Data.Vector.Generic.Sized.Internal as G
import qualified Numeric.LinearAlgebra as H
import qualified Data.List as L


--- Generic ---


-- | Matrices with static dimensions (storable).
type Matrix = G.Matrix S.Vector

-- | A fold over pairs of elements of 'Vector's of equal length.
zipFold :: (KnownNat n, Storable x, Storable y)
        => (z -> x -> y -> z)
        -> z
        -> Vector n x
        -> Vector n y
        -> z
{-# INLINE zipFold #-}
zipFold f z0 xs ys =
    let n = length xs
        foldfun z i = f z (unsafeIndex xs i) (unsafeIndex ys i)
     in L.foldl' foldfun z0 [0..n-1]

-- | Concatenates a vector of vectors into a single vector.
concat :: (KnownNat n, Storable x) => Vector m (Vector n x) -> Vector (m*n) x
{-# INLINE concat #-}
concat = G.concat

-- | Collect two values into a length 2 'Vector'.
doubleton :: Storable x => x -> x -> Vector 2 x
{-# INLINE doubleton #-}
doubleton = G.doubleton

-- | The number of rows in the 'Matrix'.
nRows :: forall m n a . KnownNat m => Matrix m n a -> Int
{-# INLINE nRows #-}
nRows = G.nRows

-- | The columns of columns in the 'Matrix'.
nColumns :: forall m n a . KnownNat n => Matrix m n a -> Int
{-# INLINE nColumns #-}
nColumns = G.nColumns

-- | Convert a 'Matrix' into a 'Vector' of 'Vector's of rows.
toRows :: (KnownNat m, KnownNat n, Storable x) => Matrix m n x -> Vector m (Vector n x)
{-# INLINE toRows #-}
toRows = G.toRows

-- | Turn a 'Vector' into a single column 'Matrix'.
columnVector :: Vector n a -> Matrix n 1 a
{-# INLINE columnVector #-}
columnVector = G.columnVector

-- | Turn a 'Vector' into a single row 'Matrix'.
rowVector :: Vector n a -> Matrix 1 n a
{-# INLINE rowVector #-}
rowVector = G.rowVector

-- | Create a 'Matrix' from a 'Vector' of 'Vector's which represent the rows.
fromRows :: (KnownNat n, Storable x) => Vector m (Vector n x) -> Matrix m n x
{-# INLINE fromRows #-}
fromRows = G.fromRows

-- | Uniform partition of an interval into a 'Vector'.
range :: (KnownNat n, Fractional x, Storable x) => x -> x -> Vector n x
{-# INLINE range #-}
range = G.range

-- | Reshapes a length 2 'Vector' into a pair of values.
toPair :: Storable x => Vector 2 x -> (x,x)
{-# INLINE toPair #-}
toPair = G.toPair


--- HMatrix ---


-- | Converts a pure, Storable-based 'Matrix' into an HMatrix matrix.
toHMatrix
    :: forall m n x . (KnownNat n, KnownNat m, H.Element x, Storable x)
    => Matrix m n x
    -> H.Matrix x
{-# INLINE toHMatrix #-}
toHMatrix (G.Matrix mtx) =
    let n = natValInt (Proxy :: Proxy n)
        m = natValInt (Proxy :: Proxy m)
     in if n == 0
           then H.fromRows $ Prelude.replicate m S.empty
           else H.reshape n $ fromSized mtx

-- | Converts an HMatrix matrix into a pure, Storable-based 'Matrix'.
fromHMatrix :: Numeric x => H.Matrix x -> Matrix m n x
{-# INLINE fromHMatrix #-}
fromHMatrix = G.Matrix . G.Vector . H.flatten

-- | Convert a 'Matrix' into a 'Vector' of 'Vector's of columns.
toColumns :: (KnownNat m, KnownNat n, Numeric x) => Matrix m n x -> Vector n (Vector m x)
{-# INLINE toColumns #-}
toColumns = toRows . transpose

-- | Create a 'Matrix' from a 'Vector' of 'Vector's which represent the columns.
fromColumns :: (KnownNat m, KnownNat n, Numeric x) => Vector n (Vector m x) -> Matrix m n x
{-# INLINE fromColumns #-}
fromColumns = transpose . fromRows

-- | Breaks a 'Vector' into a Vector of Vectors.
breakEvery :: forall n k a . (KnownNat n, KnownNat k, Storable a) => Vector (n*k) a -> Vector n (Vector k a)
{-# INLINE breakEvery #-}
breakEvery v0 =
    let k = natValInt (Proxy :: Proxy k)
        v = fromSized v0
     in generate (\i -> G.Vector $ S.unsafeSlice (finiteInt i*k) k v)


--- BLAS ---


-- | The sum of two 'Vector's.
add :: Numeric x => Vector n x -> Vector n x -> Vector n x
{-# INLINE add #-}
add (G.Vector v1) (G.Vector v2) = G.Vector (H.add v1 v2)

-- | Scalar multiplication of a 'Vector'.
scale :: Numeric x => x -> Vector n x -> Vector n x
{-# INLINE scale #-}
scale x (G.Vector v) = G.Vector (H.scale x v)

-- | Apply a 'Vector' operation to a 'Matrix'.
withMatrix :: (Vector (n*m) x -> Vector (n*m) x) -> Matrix n m x -> Matrix n m x
{-# INLINE withMatrix #-}
withMatrix f (G.Matrix v) = G.Matrix $ f v

-- | Returns the lower triangular part of a square matrix.
lowerTriangular :: forall n x . (Storable x, H.Element x, KnownNat n) => Matrix n n x -> Vector (Triangular n) x
{-# INLINE lowerTriangular #-}
lowerTriangular mtx =
    let hmtx = toHMatrix mtx
        rws = H.toRows hmtx
        rws' = Prelude.zipWith S.take [1..] rws
     in G.Vector $ S.concat rws'
--    let n = natValInt (Proxy :: Proxy n)
--        idxs = G.Vector . S.fromList
--            $ Prelude.concat [ from2Index n <$> Prelude.zip (repeat k) [0..k] | k <- [0..n-1] ]
--     in backpermute xs idxs

-- | Constructs a `Matrix` from a lower triangular part.
fromLowerTriangular :: forall n x . (Storable x, KnownNat n) => Vector (Triangular n) x -> Matrix n n x
{-# INLINE fromLowerTriangular #-}
fromLowerTriangular xs =
    let n = natValInt (Proxy :: Proxy n)
        idxs = generate (toTriangularIndex . to2Index n . finiteInt)
     in G.Matrix $ backpermute xs idxs

-- | Build a matrix with the given diagonal, lower triangular part given by the
-- first matrix, and upper triangular part given by the second matrix.
combineTriangles
    :: (KnownNat k, Storable x)
    => Vector k x -- ^ Diagonal
    -> Matrix k k x -- ^ Lower triangular source
    -> Matrix k k x -- ^ Upper triangular source
    -> Matrix k k x
{-# INLINE combineTriangles #-}
combineTriangles (G.Vector diag) crs1 crs2 =
    fromRows $ generate (generator (toRows crs1) (toRows crs2))
        where
            generator rws1 rws2 fnt =
                let (G.Vector rw1) = index rws1 fnt
                    (G.Vector rw2) = index rws2 fnt
                    i = fromIntegral fnt
                 in G.Vector $ S.take i rw1 S.++ S.cons (diag S.! i) (S.drop (i+1) rw2)

-- | The average of a 'Vector' of elements.
average :: (Numeric x, Fractional x) => Vector n x -> x
{-# INLINE average #-}
average (G.Vector v) = H.sumElements v / fromIntegral (S.length v)

-- | The dot product of two numerical 'Vector's.
dotProduct :: Numeric x => Vector n x -> Vector n x -> x
{-# INLINE dotProduct #-}
dotProduct v1 v2 = H.dot (fromSized v1) (fromSized v2)

-- | The determinant of a 'Matrix'.
diagonalMatrix :: forall n x . (KnownNat n, Field x) => Vector n x -> Matrix n n x
{-# INLINE diagonalMatrix #-}
diagonalMatrix v =
    let n = natValInt (Proxy :: Proxy n)
     in fromHMatrix $ H.diagRect 0 (fromSized v) n n

-- | The determinant of a 'Matrix'.
takeDiagonal :: (KnownNat n, Field x) => Matrix n n x -> Vector n x
{-# INLINE takeDiagonal #-}
takeDiagonal = G.Vector . H.takeDiag . toHMatrix

-- | The determinant of a 'Matrix'.
trace :: (KnownNat n, Field x) => Matrix n n x -> x
{-# INLINE trace #-}
trace = S.sum . H.takeDiag . toHMatrix

-- | Returns the eigenvalues and eigenvectors 'Matrix'.
eigens :: (KnownNat n, Field x) => Matrix n n x -> (Vector n (Complex Double), Vector n (Vector n (Complex Double)))
{-# INLINE eigens #-}
eigens mtx =
    let (exs,evs) = H.eig $ toHMatrix mtx
     in (G.Vector exs, G.Vector . S.fromList $ G.Vector <$> H.toColumns evs)

-- | Test if the matrix is semi-positive definite.
isSemiPositiveDefinite :: (KnownNat n, Field x) => Matrix n n x -> Bool
{-# INLINE isSemiPositiveDefinite #-}
isSemiPositiveDefinite =
    and . map ((0 <=) . realPart) . fst . eigens

-- | Returns the inverse, the logarithm of the absolute value of the
-- determinant, and the sign of the determinant of a given matrix.
inverseLogDeterminant :: (KnownNat n, Field x) => Matrix n n x -> (Matrix n n x, x, x)
{-# INLINE inverseLogDeterminant #-}
inverseLogDeterminant mtx =
    let (imtx,(ldet,sgn)) = H.invlndet $ toHMatrix mtx
     in (fromHMatrix imtx, ldet, sgn)

-- | The determinant of a 'Matrix'.
determinant :: (KnownNat n, Field x) => Matrix n n x -> x
{-# INLINE determinant #-}
determinant = H.det . toHMatrix

-- | Transpose a 'Matrix'.
transpose
    :: forall m n x . (KnownNat m, KnownNat n, Numeric x)
    => Matrix m n x
    -> Matrix n m x
{-# INLINE transpose #-}
transpose (G.Matrix mtx) =
    G.Matrix $ withVectorUnsafe (H.flatten . H.tr . H.reshape (natValInt (Proxy :: Proxy n))) mtx

-- | Diagonally concatenate two matrices, padding the gaps with zeroes.
diagonalConcat
    :: (KnownNat n, KnownNat m, KnownNat o, KnownNat p, Numeric x)
    => Matrix n m x -> Matrix o p x -> Matrix (n+o) (m+p) x
{-# INLINE diagonalConcat #-}
diagonalConcat mtx10 mtx20 =
    let mtx1 = toHMatrix mtx10
        mtx2 = toHMatrix mtx20
     in fromHMatrix $ H.diagBlock [mtx1,mtx2]

-- | Diagonally concatenate two matrices, padding the gaps with zeroes.
horizontalConcat
    :: (KnownNat n, KnownNat m, KnownNat o, Numeric x)
    => Matrix n m x -> Matrix n o x -> Matrix n (m+o) x
{-# INLINE horizontalConcat #-}
horizontalConcat mtx10 mtx20 =
    let mtx1 = toHMatrix mtx10
        mtx2 = toHMatrix mtx20
     in fromHMatrix $ mtx1 H.||| mtx2

-- | Diagonally concatenate two matrices, padding the gaps with zeroes.
verticalConcat
    :: (KnownNat n, KnownNat m, KnownNat o, Numeric x)
    => Matrix n o x -> Matrix m o x -> Matrix (n+m) o x
{-# INLINE verticalConcat #-}
verticalConcat mtx10 mtx20 =
    let mtx1 = toHMatrix mtx10
        mtx2 = toHMatrix mtx20
     in fromHMatrix $ mtx1 H.=== mtx2

-- | Invert a 'Matrix'.
inverse :: forall n x . (KnownNat n, Field x) => Matrix n n x -> Matrix n n x
{-# INLINE inverse #-}
inverse (G.Matrix mtx) =
    G.Matrix $ withVectorUnsafe (H.flatten . H.inv . H.reshape (natValInt (Proxy :: Proxy n))) mtx

-- | Pseudo-Invert a 'Matrix'.
pseudoInverse :: forall n x . (KnownNat n, Field x) => Matrix n n x -> Matrix n n x
{-# INLINE pseudoInverse #-}
pseudoInverse (G.Matrix mtx) =
    G.Matrix $ withVectorUnsafe (H.flatten . H.pinv . H.reshape (natValInt (Proxy :: Proxy n))) mtx

-- | Square root of a 'Matrix'.
matrixRoot :: forall n x . (KnownNat n, Field x) => Matrix n n x -> Matrix n n x
{-# INLINE matrixRoot #-}
matrixRoot (G.Matrix mtx) =
    G.Matrix $ withVectorUnsafe (H.flatten . H.sqrtm . H.reshape (natValInt (Proxy :: Proxy n))) mtx

-- | The outer product of two 'Vector's.
outerProduct :: (KnownNat m, KnownNat n, Numeric x) => Vector m x -> Vector n x -> Matrix m n x
{-# INLINE outerProduct #-}
outerProduct v1 v2 =
    fromHMatrix $ H.outer (fromSized v1) (fromSized v2)

-- | The summed outer product of two lists of 'Vector's.
sumOuterProduct :: (KnownNat m, KnownNat n, Fractional x, Numeric x) => [(Vector m x,Vector n x)] -> Matrix m n x
{-# INLINE sumOuterProduct #-}
sumOuterProduct v12s =
    let (v1s,v2s) = L.unzip v12s
        mtx1 = H.fromColumns $ fromSized <$> v1s
        mtx2 = H.fromRows $ fromSized <$> v2s
     in fromHMatrix (mtx1 H.<> mtx2)

-- | The average outer product of two lists of 'Vector's.
averageOuterProduct :: (KnownNat m, KnownNat n, Fractional x, Numeric x) => [(Vector m x,Vector n x)] -> Matrix m n x
{-# INLINE averageOuterProduct #-}
averageOuterProduct v12s =
    let (v1s,v2s) = L.unzip v12s
        mtx1 = H.fromColumns $ fromSized <$> v1s
        (_,n) = H.size mtx1
        mtx2 = H.scale (1/fromIntegral n) . H.fromRows $ fromSized <$> v2s
     in fromHMatrix (mtx1 H.<> mtx2)

-- | The average outer product of two lists of 'Vector's.
weightedAverageOuterProduct
    :: ( KnownNat m, KnownNat n, Fractional x, Numeric x )
    => [(x,Vector m x,Vector n x)]
    -> Matrix m n x
{-# INLINE weightedAverageOuterProduct #-}
weightedAverageOuterProduct wv12s =
    let (ws,v1s,v2s) = L.unzip3 wv12s
        v1s' = L.zipWith H.scale ws $ fromSized <$> v1s
        mtx1 = H.fromColumns v1s'
        mtx2 = H.fromRows $ fromSized <$> v2s
     in fromHMatrix (mtx1 H.<> mtx2)

-- | The identity 'Matrix'.
matrixIdentity :: forall n x . (KnownNat n, Numeric x, Num x) => Matrix n n x
{-# INLINE matrixIdentity #-}
matrixIdentity =
    fromHMatrix . H.ident $ natValInt (Proxy :: Proxy n)

-- | The dot products of one vector with a list of vectors.
dotMap :: (KnownNat n, Numeric x) => Vector n x -> [Vector n x] -> [x]
{-# INLINE dotMap #-}
dotMap v vs =
    let mtx' = H.fromRows $ fromSized <$> vs
     in H.toList $ mtx' H.#> fromSized v
--     in if S.null w
--           then replicate 0
--           else fmap G.Vector . H.toColumns $ toHMatrix mtx H.<> mtx'

-- | Map a linear transformation over a list of 'Vector's.
matrixMap :: (KnownNat m, KnownNat n, Numeric x)
                     => Matrix m n x -> [Vector n x] -> [Vector m x]
{-# INLINE matrixMap #-}
matrixMap mtx vs =
    let mtx' = H.fromColumns $ fromSized <$> vs
     in fmap G.Vector . H.toColumns $ toHMatrix mtx H.<> mtx'
--     in if S.null w
--           then replicate 0
--           else fmap G.Vector . H.toColumns $ toHMatrix mtx H.<> mtx'


-- | Apply a linear transformation to a 'Vector'.
matrixVectorMultiply :: (KnownNat m, KnownNat n, Numeric x)
                     => Matrix m n x -> Vector n x -> Vector m x
{-# INLINE matrixVectorMultiply #-}
matrixVectorMultiply mtx v =
    G.Vector $ toHMatrix mtx H.#> fromSized v
--    let w = toHMatrix mtx H.#> fromSized v
--     in if S.null w
--           then replicate 0
--           else G.Vector w

-- | Multiply a 'Matrix' with a second 'Matrix'.
matrixMatrixMultiply
    :: (KnownNat m, KnownNat n, KnownNat o, Numeric x)
    => Matrix m n x
    -> Matrix n o x
    -> Matrix m o x
{-# INLINE matrixMatrixMultiply #-}
matrixMatrixMultiply mtx1 mtx2 = fromHMatrix $ toHMatrix mtx1 H.<> toHMatrix mtx2

-- | Pretty print the values of a 'Matrix' (for extremely simple values of pretty).
prettyPrintMatrix :: (KnownNat m, KnownNat n, Numeric a, Show a) => Matrix m n a -> IO ()
prettyPrintMatrix = print . toHMatrix

-- | The Mean Squared difference between two vectors.
meanSquaredError
    :: KnownNat k
    => Vector k Double
    -> Vector k Double
    -> Double
{-# INLINE meanSquaredError #-}
meanSquaredError ys yhts = average $ map square (ys - yhts)

-- | L2 length of a vector.
l2Norm
    :: KnownNat k
    => Vector k Double
    -> Double
{-# INLINE l2Norm #-}
l2Norm (G.Vector xs) = H.norm_2 xs

-- | Computes the coefficient of determintation for the given outputs and model
-- predictions.
rSquared
    :: KnownNat k
    => Vector k Double -- ^ Dependent variable observations
    -> Vector k Double -- ^ Predicted Values
    -> Double -- ^ R-squared
{-# INLINE rSquared #-}
rSquared ys yhts =
    let ybr = average ys
        ssres = sum $ map square (ys - yhts)
        sstot = sum $ map (square . subtract ybr) ys
     in 1 - (ssres/sstot)

-- | Solves the linear least squares problem.
linearLeastSquares
    :: KnownNat l
    => [Vector l Double] -- ^ Independent variable observations
    -> [Double] -- ^ Dependent variable observations
    -> Vector l Double -- ^ Parameter estimates
{-# INLINE linearLeastSquares #-}
linearLeastSquares as xs =
    G.Vector $ H.fromRows (fromSized <$> as) H.<\> S.fromList xs


unsafeCholesky
    :: (KnownNat n, Field x, Storable x)
    => Matrix n n x
    -> Matrix n n x
unsafeCholesky =
    transpose . fromHMatrix . H.chol . H.trustSym . toHMatrix


--- Convolutions ---


-- | 2d cross-correlation of a kernel over a matrix of values.
crossCorrelate2d
    :: forall nk rdkr rdkc mr mc md x
    . ( KnownNat rdkr, KnownNat rdkc, KnownNat md, KnownNat mr, KnownNat mc
      , KnownNat nk, Numeric x, Storable x )
      => Proxy rdkr -- ^ Number of Kernel rows
      -> Proxy rdkc -- ^ Number of Kernel columns
      -> Proxy mr -- ^ Number of Matrix/Image rows
      -> Proxy mc -- ^ Number of Kernel/Image columns
      -> Matrix nk (md*(2*rdkr+1)*(2*rdkc+1)) x -- ^ Kernels (nk is their number)
      -> Matrix md (mr*mc) x -- ^ Image (md is the depth)
      -> Matrix nk (mr*mc) x -- ^ Cross-correlated image
{-# INLINE crossCorrelate2d #-}
crossCorrelate2d prdkr prdkc pmr pmc krns (G.Matrix v) =
    let pmd = Proxy :: Proxy md
        mtx = im2col prdkr prdkc pmd pmr pmc v
     in matrixMatrixMultiply krns mtx

-- | The transpose of a convolutional kernel.
kernelTranspose
    :: (KnownNat nk, KnownNat md, KnownNat rdkr, KnownNat rdkc, Numeric x, Storable x)
    => Proxy nk
    -> Proxy md
    -> Proxy rdkr
    -> Proxy rdkc
    -> Matrix nk (md*(2*rdkr+1)*(2*rdkc+1)) x -- ^ Kernels (nk is their number)
    -> Matrix md (nk*(2*rdkr+1)*(2*rdkc+1)) x -- ^ Kernels (nk is their number)
{-# INLINE kernelTranspose #-}
kernelTranspose pnk pmd prdkr prdkc (G.Matrix kv) = G.Matrix . backpermute kv $ kernelTransposeIndices pnk pmd prdkr prdkc

-- | 2d convolution of a kernel over a matrix of values. This is the adjoint of crossCorrelate2d.
convolve2d
    :: forall nk rdkr rdkc md mr mc x
    . ( KnownNat rdkr, KnownNat rdkc, KnownNat mr, KnownNat mc
      , KnownNat md, KnownNat nk, Numeric x, Storable x )
      => Proxy rdkr -- ^ Number of Kernel rows
      -> Proxy rdkc -- ^ Number of Kernel columns
      -> Proxy mr -- ^ Number of Matrix/Image rows
      -> Proxy mc -- ^ Number of Kernel/Image columns
      -> Matrix nk (md*(2*rdkr+1)*(2*rdkc+1)) x -- ^ Kernels (nk is their number)
      -> Matrix nk (mr*mc) x -- ^ Dual image (nk is its depth)
      -> Matrix md (mr*mc) x -- ^ Convolved image
{-# INLINE convolve2d #-}
convolve2d prdkr prdkc pmr pmc krn mtxs =
    let pnk = Proxy :: Proxy nk
        pmd = Proxy :: Proxy md
        krn' = kernelTranspose pnk pmd prdkr prdkc krn
     in crossCorrelate2d prdkr prdkc pmr pmc krn' mtxs

-- | The outer product of an image and a dual image to produce a convolutional kernel.
kernelOuterProduct
    :: forall nk rdkr rdkc md mr mc x
    . ( KnownNat rdkr, KnownNat rdkc, KnownNat mr, KnownNat mc
      , KnownNat md, KnownNat nk, Numeric x, Storable x )
      => Proxy rdkr -- ^ Number of Kernel rows
      -> Proxy rdkc -- ^ Number of Kernel columns
      -> Proxy mr -- ^ Number of Matrix/Image rows
      -> Proxy mc -- ^ Number of Kernel/Image columns
      -> Matrix nk (mr*mc) x -- ^ Dual image (nk is its depth)
      -> Matrix md (mr*mc) x -- ^ Image (md is the depth)
      -> Matrix nk (md*(2*rdkr+1)*(2*rdkc+1)) x -- ^ Kernels
{-# INLINE kernelOuterProduct #-}
kernelOuterProduct prdkr prdkc pmr pmc omtx (G.Matrix v) =
    let pmd = Proxy :: Proxy md
        imtx = im2col prdkr prdkc pmd pmr pmc v
     in matrixMatrixMultiply omtx $ transpose imtx


--- Internal ---


toTriangularIndex :: (Int,Int) -> Int
toTriangularIndex (i,j)
    | i >= j = triangularNumber i + j
    | otherwise = toTriangularIndex (j,i)

to2Index :: Int -> Int -> (Int,Int)
to2Index nj ij = divMod ij nj

to3Index :: Int -> Int -> Int -> (Int,Int,Int)
{-# INLINE to3Index #-}
to3Index nj nk ijk =
    let nj' = nj*nk
        (i,jk) = divMod ijk nj'
        (j,k) = divMod jk nk
     in (i,j,k)

from3Index :: Int -> Int -> (Int,Int,Int) -> Int
{-# INLINE from3Index #-}
from3Index nj nk (i,j,k) =
    let nj' = nj*nk
     in i*nj' + j*nk + k

to4Index :: Int -> Int -> Int -> Int -> (Int,Int,Int,Int)
{-# INLINE to4Index #-}
to4Index nj nk nl ijkl =
    let nk' = nl*nk
        nj' = nj*nk'
        (i,jkl) = divMod ijkl nj'
        (j,kl) = divMod jkl nk'
        (k,l) = divMod kl nl
     in (i,j,k,l)

from4Index :: Int -> Int -> Int -> (Int,Int,Int,Int) -> Int
{-# INLINE from4Index #-}
from4Index nj nk nl (i,j,k,l) =
    let nk' = nl*nk
        nj' = nj*nk'
     in i*nj' + j*nk' + k*nl + l

kernelTransposeIndices
    :: (KnownNat nk, KnownNat md, KnownNat rdkr, KnownNat rdkc)
    => Proxy nk
    -> Proxy md
    -> Proxy rdkr
    -> Proxy rdkc
    -> Vector (nk*md*(2*rdkr+1)*(2*rdkc+1)) Int
{-# INLINE kernelTransposeIndices #-}
kernelTransposeIndices pnk pmd prdkr prdkc =
    let nkrn = natValInt pnk
        md = natValInt pmd
        rdkr = natValInt prdkr
        rdkc = natValInt prdkc
        dmkr = 2*rdkr+1
        dmkc = 2*rdkc+1
        nl = dmkc
        nk = dmkr
        nj = nkrn
        nl' = dmkc
        nk' = dmkr
        nj' = md
        reIndex idx =
            let (i,j,k,l) = to4Index nj nk nl idx
             in from4Index nj' nk' nl' (j,i,nk-1-k,nl-1-l)
     in generate (reIndex . fromIntegral)

im2colIndices
    :: forall rdkr rdkc mr mc md
     . (KnownNat rdkr, KnownNat rdkc, KnownNat mr, KnownNat mc, KnownNat md)
    => Proxy rdkr
    -> Proxy rdkc
    -> Proxy md
    -> Proxy mr
    -> Proxy mc
    -> Vector (((2*rdkr+1)*(2*rdkc+1)*md)*(mr*mc)) Int
{-# INLINE im2colIndices #-}
im2colIndices prdkr prdkc _ pmr pmc =
    let rdkr = natValInt prdkr
        rdkc = natValInt prdkc
        nj = (2*rdkr + 1)
        nk = (2*rdkc + 1)
        reWindow idx =
            let (i,j,k) = to3Index nj nk idx
             in windowIndices prdkr prdkc pmr pmc i j k
          in (concatMap reWindow :: Vector ((2*rdkr+1)*(2*rdkc+1)*md) Int -> Vector (((2*rdkr+1)*(2*rdkc+1)*md)*(mr*mc)) Int) $ generate finiteInt

im2col
    :: forall rdkr rdkc md mr mc x
    . (KnownNat rdkr, KnownNat rdkc, KnownNat mc, KnownNat md, KnownNat mr, Num x, Storable x)
    => Proxy rdkr
    -> Proxy rdkc
    -> Proxy md
    -> Proxy mr
    -> Proxy mc
    -> Vector (md*mr*mc) x
    -> Matrix (md*(2*rdkr+1)*(2*rdkc+1)) (mr*mc) x
{-# INLINE im2col #-}
im2col prdkr prdkc pmd pmr pmc mtx =
    let idxs = im2colIndices prdkr prdkc pmd pmr pmc
        mtx' = padMatrix prdkr prdkc pmd pmr pmc mtx
     in G.Matrix $ backpermute mtx' idxs

windowIndices
    :: forall rdkr rdkc mr mc . (KnownNat rdkr, KnownNat rdkc, KnownNat mr, KnownNat mc)
    => Proxy rdkr
    -> Proxy rdkc
    -> Proxy mr
    -> Proxy mc
    -> Int
    -> Int
    -> Int
    -> Vector (mr*mc) Int
{-# INLINE windowIndices #-}
windowIndices prdkr prdkc pmr pmc kd kr kc =
    let rdkr = natValInt prdkr
        rdkc = natValInt prdkc
        mr = natValInt pmr
        mc = natValInt pmc
        mrc = mr*mc
        nj' = mr + 2*rdkr
        nk' = mc + 2*rdkc
        reIndex idx =
            let (j,k) = divMod idx mc
             in from3Index nj' nk' (kd,j+kr,k+kc)
     in G.Vector $ S.generate mrc reIndex

padMatrix
    :: forall rdkr rdkc mr mc md x
    . (KnownNat rdkr, KnownNat rdkc, KnownNat md, KnownNat mr, KnownNat mc, Num x, Storable x)
    => Proxy rdkr
    -> Proxy rdkc
    -> Proxy md
    -> Proxy mr
    -> Proxy mc
    -> Vector (md*mr*mc) x
    -> Vector (md*(mr + 2*rdkr)*(mc + 2*rdkc)) x
{-# INLINE padMatrix #-}
padMatrix _ _ _ _ _ v =
    let mtxs :: Vector md (Matrix mr mc x)
        mtxs = map G.Matrix $ breakEvery v
        pdrs :: Vector rdkr (Vector mc x)
        pdrs = replicate $ replicate 0
        mtxs' = map (\mtx -> fromRows $ pdrs ++ toRows mtx ++ pdrs) mtxs
        pdcs :: Vector rdkc (Vector (mr + 2*rdkr) x)
        pdcs = replicate $ replicate 0
     in concatMap G.toVector $ map (\mtx' -> G.fromColumns $ pdcs ++ G.toColumns mtx' ++ pdcs) mtxs'