{-# LANGUAGE ViewPatterns #-}
--------------------------------------------------------------------------------
-- |
-- Module      : ArrayFire.Sparse
-- Copyright   : David Johnson (c) 2019-2020
-- License     : BSD3
-- Maintainer  : David Johnson <djohnson.m@gmail.com>
-- Stability   : Experimental
-- Portability : GHC
--
-- [ArrayFire Docs](http://arrayfire.org/docs/group__sparse__func.htm)
-- Functions to create and handle sparse arrays and matrix operations.
--
-- *Note*
-- Sparse functionality support was added to ArrayFire in v3.4.0.
--
-- >>> createSparseArray 10 10 (matrix @Double (10,10) [[1,2],[3,4]]) (vector @Int32 10 [1..]) (vector @Int32 10 [1..]) CSR
--
--
--------------------------------------------------------------------------------
module ArrayFire.Sparse where

import ArrayFire.Types
import ArrayFire.FFI
import ArrayFire.Internal.Sparse
import ArrayFire.Internal.Types
import Data.Int

-- | This function converts af::array of values, row indices and column indices into a sparse array.
--
-- [ArrayFire Docs](http://arrayfire.org/docs/group__sparse__func__create.htm#ga42c5cf729a232c1cbbcfe0f664f3b986)
--
-- *Note*
-- This function only create references of these arrays into the sparse data structure and does not do deep copies.
--
-- >>> createSparseArray 10 10 (matrix @Double (10,10) [[1,2],[3,4]]) (vector @Int32 10 [1..]) (vector @Int32 10 [1..]) CSR
--
createSparseArray
  :: (AFType a, Fractional a)
  => Int
  -- ^ is the number of rows in the dense matrix
  -> Int
  -- ^ is the number of columns in the dense matrix
  -> Array a
  -- ^ is the 'Array' containing the non-zero elements of the matrix
  -> Array Int32
  -- ^ is the row indices for the sparse array
  -> Array Int32
  -- ^ the column indices for the sparse array
  -> Storage
  -- ^ the storage format of the sparse array
  -> Array a
  -- ^ Sparse Array
createSparseArray (fromIntegral -> r) (fromIntegral -> c) arr1 arr2 arr3 s =
  op3Int arr1 arr2 arr3 (\p ar1 ar2 ar3 -> af_create_sparse_array p r c ar1 ar2 ar3 (toStorage s))

-- af_err af_create_sparse_array_from_ptr(af_array *out, const dim_t nRows, const dim_t nCols, const dim_t nNZ, const void * const values, const int * const rowIdx, const int * const colIdx, const af_dtype type, const af_storage stype, const af_source src);

-- | This function converts a dense af_array into a sparse array.
--
-- [ArrayFire Docs](http://arrayfire.org/docs/group__sparse__func__create.htm#ga52e3b2895cf9e9d697a06b4b44190d92)
--
-- *Note*
-- This function only create references of these arrays into the sparse data structure and does not do deep copies.
--
-- >>> createSparseArrayFromDense (matrix @Double (2,2) [[1,2],[3,4]]) CSR
-- ArrayFire Array
-- Storage Format : AF_STORAGE_CSR
-- [2 2 1 1]
-- ArrayFire Array: Values
-- [4 1 1 1]
--     1.0000     3.0000     2.0000     4.0000
-- ArrayFire Array: RowIdx
-- [3 1 1 1]
--          0          2          4
-- ArrayFire Array: ColIdx
-- [4 1 1 1]
--          0          1          0          1
--
createSparseArrayFromDense
  :: (AFType a, Fractional a)
  => Array a
  -- ^ is the source dense matrix
  -> Storage
  -- ^ is the storage format of the sparse array
  -> Array a
  -- ^ 'Array' for the sparse array with the given storage type
createSparseArrayFromDense a s =
  a `op1` (\p x -> af_create_sparse_array_from_dense p x (toStorage s))

-- | Convert an existing sparse array into a different storage format.
--
-- [ArrayFire Docs](http://arrayfire.org/docs/group__sparse__func__convert__to.htm)
--
-- Converting storage formats is allowed between 'CSR', 'COO' and DENSE.
--
-- When converting to DENSE, a dense array is returned.
--
-- *Note*
-- 'CSC' is currently not supported.
--
-- >>> array = createSparseArrayFromDense (matrix @Double (2,2) [[1,2],[3,4]]) CSR
-- >>> array
-- ArrayFire Array
-- Storage Format : AF_STORAGE_CSR
-- [2 2 1 1]
-- ArrayFire Array: Values
-- [4 1 1 1]
--     1.0000     3.0000     2.0000     4.0000
-- ArrayFire Array: RowIdx
-- [3 1 1 1]
--          0          2          4
-- ArrayFire Array: ColIdx
-- [4 1 1 1]
--          0          1          0          1
--
-- >>> sparseConvertTo array COO
-- ArrayFire Array
-- Storage Format : AF_STORAGE_COO
-- [2 2 1 1]
-- ArrayFire Array: Values
-- [4 1 1 1]
--     1.0000     2.0000     3.0000     4.0000
-- ArrayFire Array: RowIdx
-- [4 1 1 1]
--          0          1          0          1
-- ArrayFire Array: ColIdx
-- [4 1 1 1]
--          0          0          1          1
--
sparseConvertTo
  :: (AFType a, Fractional a)
  => Array a
  -- ^ is the source sparse matrix to be converted
  -> Storage
  -- ^ is the storage format of the output sparse array
  -> Array a
  -- ^ the sparse array with the given storage type
sparseConvertTo a s =
  a `op1` (\p x -> af_sparse_convert_to p x (toStorage s))

-- | Returns a dense array from a sparse input
--
-- [ArrayFire Docs](http://arrayfire.org/docs/group__sparse__func__dense.htm#ga80c3d8db78d537b74d9caebcf359b6a5)
--
-- Converts the sparse matrix into a dense matrix and returns it
--
-- >>> array = createSparseArrayFromDense (matrix @Double (2,2) [[1,2],[3,4]]) CSR
-- >>> array
-- ArrayFire Array
-- Storage Format : AF_STORAGE_CSR
-- [2 2 1 1]
-- ArrayFire Array: Values
-- [4 1 1 1]
--     1.0000     3.0000     2.0000     4.0000
-- ArrayFire Array: RowIdx
-- [3 1 1 1]
--          0          2          4
-- ArrayFire Array: ColIdx
-- [4 1 1 1]
--          0          1          0          1
--
-- >>> sparseToDense array
-- ArrayFire Array
-- [2 2 1 1]
--     1.0000     2.0000
--     3.0000     4.0000
--
sparseToDense
  :: (AFType a, Fractional a)
  => Array a
  -> Array a
sparseToDense = (`op1` af_sparse_to_dense)

-- | Returns reference to components of the input sparse array.
--
-- [ArrayFire Docs](http://arrayfire.org/docs/group__sparse__func__info.htm#gae6b553df80e21c174d374e82d8505ba5)
--
-- Returns reference to values, row indices, column indices and storage format of an input sparse array
--
-- >>> (values, cols, rows, storage) = sparseGetInfo $ createSparseArrayFromDense (matrix @Double (2,2) [[1,2],[3,4]]) CSR
-- >>> values
-- ArrayFire Array
-- [4 1 1 1]
--    1.0000     3.0000     2.0000     4.0000
--
-- >>> cols
-- ArrayFire Array
-- [3 1 1 1]
--         0          2          4
--
-- >>> rows
-- ArrayFire Array
-- [4 1 1 1]
--         0          1          0          1
--
-- >>> storage
-- CSR
--
sparseGetInfo
  :: (AFType a, Fractional a)
  => Array a
  -> (Array a, Array a, Array a, Storage)
sparseGetInfo x = do
  let (a,b,c,d) = x `op3p1` af_sparse_get_info
  (a,b,c,fromStorage d)

-- | Returns reference to the values component of the sparse array.
--
-- [ArrayFire Docs](http://arrayfire.org/docs/group__sparse__func__values.htm)
--
-- Returns reference to the values component of the sparse array.
-- Values is the 'Array' containing the non-zero elements of the dense matrix.
--
-- >>> sparseGetValues (createSparseArrayFromDense (matrix @Double (2,2) [[1,2],[3,4]]) CSR)
-- ArrayFire Array
-- [4 1 1 1]
--    1.0000     3.0000     2.0000     4.0000
--
sparseGetValues
  :: (AFType a, Fractional a)
  => Array a
  -> Array a
sparseGetValues = (`op1` af_sparse_get_values)

-- | Returns reference to the row indices component of the sparse array. More...
--
-- [ArrayFire Docs](http://arrayfire.org/docs/group__sparse__func__row__idx.htm)
--
-- Returns reference to the row indices component of the sparse array.
-- Row indices is the 'Array' containing the column indices of the sparse array.
--
-- >>> sparseGetRowIdx (createSparseArrayFromDense (matrix @Double (2,2) [[1,2],[3,4]]) CSR)
-- ArrayFire Array
-- [3 1 1 1]
--         0          2          4
--
sparseGetRowIdx
  :: (AFType a, Fractional a)
  => Array a
  -> Array a
sparseGetRowIdx = (`op1` af_sparse_get_row_idx)

-- | Returns reference to the column indices component of the sparse array. More...
--
-- [ArrayFire Docs](http://arrayfire.org/docs/group__sparse__func__col__idx.htm)
--
-- Returns reference to the column indices component of the sparse array.
-- Column indices is the 'Array' containing the column indices of the sparse array.
--
-- >>> sparseGetColIdx (createSparseArrayFromDense (matrix @Double (2,2) [[1,2],[3,4]]) CSR)
-- ArrayFire Array
-- [4 1 1 1]
--         0          1          0          1
--
sparseGetColIdx
  :: (AFType a, Fractional a)
  => Array a
  -> Array a
sparseGetColIdx = (`op1` af_sparse_get_col_idx)

-- | Returns the storage type of a sparse array.
--
-- [ArrayFire Docs](http://arrayfire.org/docs/group__sparse__func__storage.htm)
--
-- Returns the number of non zero elements in the sparse array.
-- This is always equal to the size of the values array.
--
-- >>> sparseGetStorage $ createSparseArrayFromDense (matrix @Double (2,2) [[1,2],[3,4]]) CSR
-- CSR
--
sparseGetStorage
  :: (AFType a, Fractional a)
  => Array a
  -> Storage
sparseGetStorage a =
  fromStorage (a `infoFromArray` af_sparse_get_storage)

-- | Returns the number of non zero elements in the sparse array. More...
--
-- [ArrayFire Docs](http://arrayfire.org/docs/group__sparse__func__nnz.htm#ga0c1ad61d829c02a280c28820eb91f03e)
--
-- Returns the number of non zero elements in the sparse array.
-- This is always equal to the size of the values array.
--
-- >>> sparseGetNNZ $ createSparseArrayFromDense (matrix @Double (2,2) [[1,2],[3,4]]) CSR
-- 4
--
sparseGetNNZ
  :: (AFType a, Fractional a)
  => Array a
  -> Int
sparseGetNNZ a =
  fromIntegral (a `infoFromArray` af_sparse_get_nnz)

