-----------------------------------------------------------------------------
-- |
-- Module      :  Data.Packed.Static.Vector
-- Copyright   :  (c) Reiner Pope 2008
-- License     :  GPL-style
--
-- Maintainer  :  Reiner Pope <reiner.pope@gmail.com>
-- Stability   :  experimental
-- Portability :  portable
--
-- Statically-dimensioned 1D vectors.
--
-----------------------------------------------------------------------------

module Data.Packed.Static.Vector(
   Vector,
   -- * Shaping
   -- | Functions manipulating a vector's (static) shape.
   refineVec,
   atDim,
   atShape,
   -- * Construction by index
   buildVector,
   -- * To/from lists
   fromListU,
   toList,
   -- * Manipulation
   dim,
   (@>),
   subVectorU,
   joinU,
   constant,
   linspace,
   vectorMin,
   vectorMax,
   vectorMinIndex,
   vectorMaxIndex,
   liftVector,
   liftVector2,
 ) where

import Data.Maybe(fromJust)
import Data.List(intercalate)

import qualified Numeric.LinearAlgebra as H

import Data.Packed.Static.Shapes
import Data.Packed.Static.Imports
import Data.Packed.Static.Internal

instance ShapedContainer Vector where
    type Unwrapped Vector = H.Vector
    unWrap = unVector
    wrapU = Vector
                   
    type UnknownShape Vector = Unknown
    unsafeReshape = Vector . unVector

------ Shaping
-- | \"Reifies\" a Vector's length in types. Useful when vectors of length "Unknown"
-- need to be used for a statically-sized operations. For instance, if @v :: Vector Unknown Double@, 
-- then we can write
-- 
--    @refineVec v (\v -> forgetSize $ v + constant 5)@
-- 
-- to add a constant vector of 5s with the appropriate size.
refineVec :: forall m t a. Vector m t -> (forall n. PositiveT n => Vector n t -> a) -> a
refineVec v k = fromJust $ reifyPositiveD (toInteger $ dim v) (\n -> k (unsafeReshape v `atShape` n))

withDim :: forall n a. PositiveT n => (Int -> Vector n a) -> Vector n a
withDim f = f n where
    n = fromIntegerT (undefined :: n)

{- | Sets an arbitrary-length vector to a specific value.

@\> constant 1 `atDim` 5
[$vec| 1.0, 1.0, 1.0, 1.0, 1.0 |]@
-}
atDim :: (forall n. PositiveT n => Vector n t) -> Int -> Vector Unknown t
atDim v n | n > 0     = fromJust $ reifyPositiveD (toInteger n) (\n -> forgetShapeU $ v `atShape` n)
          | otherwise = error $ "atDim: negative vector length: " ++ show n

------- By-index construction
{- | Builds a vector given a function from indices. Indexing is 0-based.

@\> buildVector fromIntegral \`atShape\` d5
[$vec| 0.0, 1.0, 2.0, 3.0, 4.0 |]@
-}
buildVector :: (PositiveT n, Element a) => (Int -> a) -> Vector n a
buildVector f = withDim (\len -> Vector $ H.buildVector len f)


-------- To / from lists
{- | Constructs a vector from all the elements of a list.

@\> fromListU [1,2,3,4,5]
[$vec| 1.0, 2.0, 3.0, 4.0, 5.0 |]@
-}
fromListU :: (Storable a) => [a] -> Vector Unknown a
fromListU = Vector . H.fromList

{- | Converts to a list of elements.

@\> toList [$vec|1,2,3|]
[1.0,2.0,3.0]@
-}
toList :: (Storable a) => Vector n a -> [a]
toList = H.toList . unVector

------ Other operations
{- | Vector's length.

@\> dim [$vec|1::Double,2,3|]
3@
-}
dim :: Vector n t -> Int
dim = H.dim . unVector

{- | Indexes a vector.

@\> [$vec|1,2,3|] \@\> 1
2.0@
-}
(@>) :: (Storable t) => Vector n t -> Int -> t
(@>) = (H.@>) . unVector

{- | Extracts a subvector.

@\> subVectorU 2 3 [$vec|1,2,3,4,5|]
[$vec| 3.0, 4.0, 5.0 |]@
-}
subVectorU :: (Storable t) => 
              Int -- ^ Initial index
           -> Int -- ^ Length of resultant vector
           -> Vector n t
           -> Vector Unknown t
subVectorU a b = Vector . H.subVector a b . unVector

--- I say the input Size is unknown, although it can be anything,
--- so users don't think all the vectors must be of the same size.

{- | Joins each vector in the list.

@\> joinU [[$vecU|1,2,3|], [$vecU|4,5|]]
[$vec| 1.0, 2.0, 3.0, 4.0, 5.0 |]@
-}
joinU :: (Storable t) => [Vector Unknown t] -> Vector Unknown t
joinU = Vector . H.join . map unVector

{- | Creates a constant vector of any length. The length is
determined by the type.

@\> [$vec|1,2,3|] + constant 2
[$vec| 3.0, 4.0, 5.0 |]@
-}
constant :: (Element t, PositiveT n) => t -> Vector n t
constant a = withDim (Vector . H.constant a)

{- | Creates a vector of arbitrary length whose
components range linearly from a to b. The vector's
length is determined by its type.

@\> linspace (1,5) `atShape` d4
[$vec| 1.0, 2.333333333333333, 3.6666666666666665, 5.0 |]@
-}
linspace :: (PositiveT n) => (Double,Double) -> Vector n Double
linspace r = withDim (\n -> Vector $ H.linspace n r) where

{- | Gives the vector's minimum entry.

@\> vectorMin [$vec|1,2,3|]
1.0@
-}
vectorMin :: Vector n Double -> Double
vectorMin = H.vectorMin . unVector

{- | Gives the vector's maximum entry.

@\> vectorMax [$vec|1,2,3|]
3.0@
-}
vectorMax :: Vector n Double -> Double
vectorMax = H.vectorMax . unVector

{- | Gives the index of a vector's minimum entry.
@\> vectorMinIndex [$vec|1,2,3|]
0@
-}
vectorMinIndex :: Vector n Double -> Int
vectorMinIndex = H.vectorMinIndex . unVector

{- | Gives the index of a vector's maximum entry. 

@\> vectorMaxIndex [$vec|1,2,3|]
2@
-}
vectorMaxIndex :: Vector n Double -> Int
vectorMaxIndex = H.vectorMaxIndex . unVector

{- | 'map' for vectors.

@\> (*2) `liftVector` [$vec|1,2,3|]
[$vec| 2.0, 4.0, 6.0 |]@
-}
liftVector :: (Storable a, Storable b) => (a -> b) -> Vector n a -> Vector n b
liftVector f = Vector . H.mapVector f . unVector

--- note: this requires they are of the same size, whereas hmatrix allows
--- different sizes; it uses the minimum size.
{- | 'zipWith' for vectors. 

@\> liftVector2 (+) [$vec|1,2,3|] (constant 3)
[$vec| 4.0, 5.0, 6.0 |]@
-}
liftVector2 :: (Storable a, Storable b, Storable c) =>
               (a -> b -> c) -> Vector n a -> Vector n b -> Vector n c
liftVector2 f v1 v2 = Vector $ H.zipVector f (unVector v1) (unVector v2)

instance (Storable e, Show e) => Show (Vector n e) where
    show v = "[$vec| " ++ intercalate ", " (map show $ toList v) ++ " |]"