numhask-array 0.4.0.0 → 0.5.0.0
raw patch · 8 files changed
+980/−199 lines, 8 filesdep ~numhask-array
Dependency ranges changed: numhask-array
Files
- numhask-array.cabal +5/−3
- readme.md +36/−0
- src/NumHask/Array.hs +15/−45
- src/NumHask/Array/Dynamic.hs +83/−58
- src/NumHask/Array/Fixed.hs +130/−66
- src/NumHask/Array/HMatrix.hs +634/−24
- src/NumHask/Array/Shape.hs +66/−3
- stack.yaml +11/−0
numhask-array.cabal view
@@ -1,5 +1,5 @@ name: numhask-array-version: 0.4.0.0+version: 0.5.0.0 synopsis: n-dimensional arrays description:@@ -26,6 +26,9 @@ Simple cabal-version: 1.18+extra-source-files:+ readme.md+ stack.yaml source-repository head type: git@@ -76,9 +79,8 @@ UnicodeSyntax build-depends: base >=4.11 && <5- , adjunctions >=4.0 && <5 , doctest >=0.13 && <0.17- , numhask-array >=0.3 && <0.5+ , numhask-array >=0.3 && <0.6 , numhask-prelude >=0.3 && <0.4 , numhask-hedgehog >=0.3 && <0.4 , hedgehog >=0.5 && <1.1
+ readme.md view
@@ -0,0 +1,36 @@+numhask-array+===++[](https://travis-ci.org/tonyday567/numhask) +[](https://hackage.haskell.org/package/numhask-array) ++Arrays are higher-kinded numbers that can be indexed into with an Int list. Higher-kinded numbers are things with a non-primitive type that we wish to use the usual numerical operators on (+,-,*,/,abs).++This is an experimental library that:+- allows shape to be specified at both the type and value level.+- provides operators at value and type level to help manipulate shapes.+- Provides fixed and dynamic arrays with the same API.++Performance experiments are located in [numhask-bench](https://github.com/tonyday567/numhask-bench)++Usefulness of the array language that results from this treatment is yet to be explored.++API of an array language+---++https://en.wikipedia.org/wiki/APL_(programming_language)++See http://hiperfit.dk/pdf/array14_final.pdf for context and a sketch of an intermediate typed array language effort.++The operators that result from using the Representable type - separation of size tracking at compile level, from computational at runtime - ends up looking like APL.++Matrix multiplication in APL is `+.x` and in numhask-array is `dot sum (*)`. There is a slight increase in abstraction by explicitly exposing the fold in the algorithm, but the expressions are both very neat and abstracted away from the specialisation of multiplying matrices.++References+---++https://blog.plover.com/prog/apl-matrix-product.html++https://en.wikipedia.org/wiki/Tensor_contraction++https://en.wikipedia.org/wiki/Tensor_(intrinsic_definition)#Definition:_Tensor_Product_of_Vector_Spaces
src/NumHask/Array.hs view
@@ -1,64 +1,34 @@ {-# OPTIONS_GHC -Wall #-} --- | Higher-kinded numbers that can be represented with an [Int] index into elements.+-- | Numbers that can be indexed into with an Int list. --+-- module NumHask.Array- ( -- * Shapes+ ( -- * Imports --- -- $shape- module NumHask.Array.Shape,-- -- * Numbers with a fixed shape+ -- $imports --- -- $fixed+ module NumHask.Array.Shape, module NumHask.Array.Fixed,-- -- * Numbers with a dynamic shape- --- -- $dynamic-- -- * HMatrix API- --- -- $hmatrix- ) where import NumHask.Array.Shape import NumHask.Array.Fixed --- $shape------ Using [`Int`] as the index for an array nicely represents the practical interests and constraints downstream of this high-level API: densely-packed numbers (reals or integrals), indexed and layered.---- $fixed+-- $imports ----- A design principle of modern haskell (synonymous with ghc usage) is to push computation into compilation. `Representable` is an iconic example. `Shape` must ultimately be known at compile-time, but if it is, we can dispense with a large classes of runtime check which slow pipelines. This tends to create computation where shape is abstracted and we fold algorithms in and out of structures.---- $dynamic+-- > import NumHask.Array ----- | In many situations, where shape is being tracked or otherwise only known at runtime, a clear module arrangement is:+-- imports the fixed version of `NumHask.Array.Fixed.Array` and `Shape` ----- >>> import NumHask.Array.Shape--- >>> import qualified NumHask.Array.Fixed as F--- >>> import qualified NumHask.Array.Dynamic as D---- $hmatrix+-- In many situations, where shape is being tracked or otherwise only known at runtime, a clear module arrangement is: ----- | hmatrix remains the speed king within haskell, and numhask-array is no exception. If speed matters then:+-- > import NumHask.Array.Shape+-- > import qualified NumHask.Array.Fixed as F+-- > import qualified NumHask.Array.Dynamic as D ----- >>> import NumHask.Array.Shape--- >>> import qualified NumHask.Array.HMatrix as H+-- A hmatrix instance of Array is also provided for performance purposes: ----- will deliver the NumHask API over a HMatrix representation, and then the current fastest way to multiply matices in boiler-plate haskell.+-- > import NumHask.Array.Shape+-- > import qualified NumHask.Array.HMatrix as H ----- FIXME: fill out Fixed API----------
src/NumHask/Array/Dynamic.hs view
@@ -3,7 +3,6 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GADTs #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE NoStarIsType #-}@@ -13,22 +12,22 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# OPTIONS_GHC -Wno-redundant-constraints #-}-{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}+{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-} --- | Arrays with a dynamic shape+-- | Arrays with a dynamic shape. module NumHask.Array.Dynamic- ( -- * Dynamic Arrays- --- -- $array- Array(..),+ ( -- $setup+ Array (..),++ -- * Conversion fromFlatList, -- * Operators- --- -- $operators reshape, transpose, diag,+ ident,+ singleton, selects, selectsExcept, folds,@@ -42,28 +41,30 @@ expand, contract, dot,+ mult, slice, squeeze,- singleton,- ident,+ -- * Scalar --- -- $scalar+ -- Scalar specialisations fromScalar, toScalar,+ -- * Matrix --- -- $matrix+ -- Matrix specialisations. col, row, mmult,- )where+ )+where +import Data.List ((!!)) import qualified Data.Vector as V import GHC.Show (Show (..)) import NumHask.Array.Shape import NumHask.Prelude as P hiding (product, transpose)-import Data.List ((!!)) -- $setup -- >>> :set -XDataKinds@@ -77,6 +78,7 @@ -- | a multidimensional array with a value-level shape --+-- >>> let a = fromFlatList [2,3,4] [1..24] :: Array Int -- >>> a -- [[[1, 2, 3, 4], -- [5, 6, 7, 8],@@ -142,7 +144,7 @@ tabulate :: () => [Int] -> ([Int] -> a) -> Array a tabulate ds f = Array ds . V.generate (size ds) $ (f . shapen ds) --- | reshape an array (with the same number of elements)+-- | Reshape an array (with the same number of elements). -- -- >>> reshape [4,3,2] a -- [[[1, 2],@@ -163,14 +165,14 @@ Array a reshape s a = tabulate s (index a . shapen (shape a) . flatten s) --- | reverse indices eg transposes the element /Aijk/ to /Akji/+-- | Reverse indices eg transposes the element A/ijk/ to A/kji/. -- -- >>> index (transpose a) [1,0,0] == index a [0,0,1] -- True transpose :: Array a -> Array a transpose a = tabulate (reverse $ shape a) (index a . reverse) --- |+-- | The identity array. -- -- >>> ident [3,2] -- [[1, 0],@@ -184,7 +186,7 @@ isDiag [x, y] = x == y isDiag (x : y : xs) = x == y && isDiag (y : xs) --- | extract the diagonal+-- | Extract the diagonal of an array. -- -- >>> diag (ident [3,2]) -- [1, 1]@@ -196,7 +198,8 @@ go [] = throw (NumHaskException "Rank Underflow") go (s' : _) = index a (replicate (rank (shape a)) s') --- |+-- | Create an array composed of a single value.+-- -- >>> singleton [3,2] one -- [[1, 1], -- [1, 1],@@ -204,7 +207,7 @@ singleton :: [Int] -> a -> Array a singleton ds a = tabulate ds (const a) --- | /selects ds ps a/ select from /a/, elements along /ds/ dimensions at positions /ps/+-- | Select an array along dimensions. -- -- >>> let s = selects [0,1] [1,1] a -- >>> s@@ -218,7 +221,7 @@ where go s = index a (addIndexes s ds i) --- | select an index /except/ along dimensions+-- | Select an index /except/ along specified dimensions -- -- >>> let s = selectsExcept [2] [1,1] a -- >>> s@@ -230,7 +233,7 @@ Array a selectsExcept ds i a = selects (exclude (rank (shape a)) ds) i a --- | fold along specified dimensions+-- | Fold along specified dimensions. -- -- >>> folds sum [1] a -- [68, 100, 132]@@ -243,7 +246,7 @@ where go s = f (selects ds s a) --- | extracts dimensions to an outer layer+-- | Extracts dimensions to an outer layer. -- -- >>> let e = extracts [1,2] a -- >>> shape <$> extracts [0] a@@ -256,7 +259,7 @@ where go s = selects ds s a --- | extracts /except/ dimensions to an outer layer+-- | Extracts /except/ dimensions to an outer layer. -- -- >>> let e = extractsExcept [1,2] a -- >>> shape <$> extracts [0] a@@ -267,7 +270,7 @@ Array (Array a) extractsExcept ds a = extracts (exclude (rank (shape a)) ds) a --- | join inner and outer dimension layers+-- | Join inner and outer dimension layers. -- -- >>> let e = extracts [1,0] a -- >>> let j = joins [1,0] e@@ -283,7 +286,7 @@ so = shape a si = shape (index a (replicate (rank so) 0)) --- | maps along specified dimensions+-- | Maps a function along specified dimensions. -- -- >>> shape $ maps (transpose) [1] a -- [4,3,2]@@ -294,7 +297,7 @@ Array b maps f ds a = joins ds (fmap f (extracts ds a)) --- | concatenate along a dimension+-- | Concatenate along a dimension. -- -- >>> shape $ concatenate 1 a a -- [2,6,4]@@ -319,7 +322,7 @@ ((s !! d) >= (ds0 !! d)) ds0 = shape a0 --- | /insert d i/ insert along the dimension /d/ at position /i/+-- | Insert along a dimension at a position. -- -- >>> insert 2 0 a (fromFlatList [2,3] [100..105]) -- [[[100, 1, 2, 3, 4],@@ -341,7 +344,7 @@ | s !! d < i = index a s | otherwise = index a (decAt d s) --- | insert along a dimension at the end+-- | Insert along a dimension at the end. -- -- >>> append 2 a (fromFlatList [2,3] [100..105]) -- [[[1, 2, 3, 4, 100],@@ -377,8 +380,9 @@ where go s = index a (addIndexes [] ds s) --- | product two arrays using the supplied binary function--- If the function is multiply, and the arrays are tensors,+-- | Product two arrays using the supplied binary function.+--+-- For context, if the function is multiply, and the arrays are tensors, -- then this can be interpreted as a tensor product. -- -- https://en.wikipedia.org/wiki/Tensor_product@@ -399,9 +403,9 @@ where r = rank (shape a) --- | contract an array by applying the supplied (folding) function on diagonal elements of the dimensions.+-- | Contract an array by applying the supplied (folding) function on diagonal elements of the dimensions. ----- This generalises a tensor contraction by allowing the number of contracting diagonals to be other than 2, and allowing another binary other than addition+-- This generalises a tensor contraction by allowing the number of contracting diagonals to be other than 2, and allowing a binary operator other than multiplication. -- -- >>> let b = fromFlatList [2,3] [1..6] :: Array Int -- >>> contract sum [1,2] (expand (*) b (transpose b))@@ -414,22 +418,22 @@ Array b contract f xs a = f . diag <$> extractsExcept xs a --- | a generalisation of a dot operation, which is a multiplicative expansion of two arrays and sum contraction along the middle two dimensions.+-- | A generalisation of a dot operation, which is a multiplicative expansion of two arrays and sum contraction along the middle two dimensions. ----- dot sum (*) on two matrices is known as matrix multiplication+-- matrix multiplication -- -- >>> let b = fromFlatList [2,3] [1..6] :: Array Int -- >>> dot sum (*) b (transpose b) -- [[14, 32], -- [32, 77]] ----- dot sum (*) on two vectors is known as the inner product+-- inner product -- -- >>> let v = fromFlatList [3] [1..3] :: Array Int -- >>> dot sum (*) v v -- 14 ----- dot sum (*) m v on a matrix and a vector is matrix-vector multiplication+-- matrix-vector multiplication -- Note that an `Array Int` with shape [3] is neither a row vector nor column vector. `dot` is not turning the vector into a matrix and then using matrix multiplication. -- -- >>> dot sum (*) v b@@ -447,15 +451,45 @@ where sa = shape a --- | select elements along every dimension+-- | Array multiplication. --+-- matrix multiplication+--+-- >>> let b = fromFlatList [2,3] [1..6] :: Array Int+-- >>> mult b (transpose b)+-- [[14, 32],+-- [32, 77]]+--+-- inner product+--+-- >>> let v = fromFlatList [3] [1..3] :: Array Int+-- >>> mult v v+-- 14+--+-- matrix-vector multiplication+--+-- >>> mult v b+-- [9, 12, 15]+--+-- >>> mult b v+-- [14, 32]+mult ::+ ( Additive a,+ Multiplicative a+ ) =>+ Array a ->+ Array a ->+ Array a+mult = dot sum (*)++-- | Select elements along positions in every dimension.+-- -- >>> let s = slice [[0,1],[0,2],[1,2]] a -- >>> s -- [[[2, 3], -- [10, 11]], -- [[14, 15], -- [22, 23]]]--- slice :: [[Int]] -> Array a ->@@ -464,7 +498,7 @@ where go s = index a (zipWith (!!) pss s) --- | remove singleton dimensions+-- | Remove single dimensions. -- -- >>> let a' = fromFlatList [2,1,3,4,1] [1..24] :: Array Int -- >>> shape $ squeeze a'@@ -474,13 +508,7 @@ Array a squeeze (Array s x) = Array (squeeze' s) x --- * scalar specializations---- | <https://en.wikipedia.org/wiki/Scalarr_(mathematics) Wiki Scalar>------ An /Array/ with shape [] despite being a Scalar is nevertheless a one-element vector under the hood.---- | unwrapping scalars is probably a performance bottleneck+-- | Unwrapping scalars is probably a performance bottleneck. -- -- >>> let s = fromFlatList [] [3] :: Array Int -- >>> fromScalar s@@ -488,22 +516,21 @@ fromScalar :: Array a -> a fromScalar a = index a ([] :: [Int]) --- | convert a number to a scalar+-- | Convert a number to a scalar. -- -- >>> :t toScalar 2 -- toScalar 2 :: Num a => Array a toScalar :: a -> Array a toScalar a = fromFlatList [] [a] --- * matrix specializations--- | extract specialised to a matrix+-- | Extract specialised to a matrix. -- -- >>> row 1 m -- [4, 5, 6, 7] row :: Int -> Array a -> Array a row i (Array s a) = Array [n] $ V.slice (i * n) n a where- [_,n] = s+ (_ : n : _) = s -- | extract specialised to a matrix --@@ -512,7 +539,7 @@ col :: Int -> Array a -> Array a col i (Array s a) = Array [m] $ V.generate m (\x -> V.unsafeIndex a (i + x * n)) where- [m,n] = s+ (m : n : _) = s -- | matrix multiplication --@@ -536,13 +563,11 @@ Array a -> Array a -> Array a-mmult (Array sx x) (Array sy y) = tabulate [m,n] go+mmult (Array sx x) (Array sy y) = tabulate [m, n] go where go [] = throw (NumHaskException "Needs two dimensions") go [_] = throw (NumHaskException "Needs two dimensions") go (i : j : _) = sum $ V.zipWith (*) (V.slice (fromIntegral i * k) k x) (V.generate k (\x' -> y V.! (fromIntegral j + x' * n)))- [m,k] = sx- [_,n] = sy+ (m : k : _) = sx+ (_ : n : _) = sy {-# INLINE mmult #-}--
src/NumHask/Array/Fixed.hs view
@@ -1,11 +1,8 @@ {-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveFoldable #-}-{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NoImplicitPrelude #-}@@ -17,23 +14,24 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# OPTIONS_GHC -Wno-redundant-constraints #-}+{-# OPTIONS_GHC -fno-warn-unused-imports #-} -- | Arrays with a fixed shape. module NumHask.Array.Fixed- ( -- * Fixed-sixed arrays- --- -- $array+ ( -- $setup Array (..),++ -- * Conversion with, shape, toDynamic, -- * Operators- --- -- $operators reshape, transpose, diag,+ ident,+ singleton, selects, selectsExcept, folds,@@ -47,35 +45,35 @@ expand, contract, dot,+ mult, slice, squeeze,- ident,- singleton, -- * Scalar --- -- $scalar+ -- Scalar specialisations Scalar, fromScalar, toScalar, -- * Vector --- -- $scalar+ -- Vector specialisations. Vector, -- * Matrix --- -- $matrix+ -- Matrix specialisations. Matrix, col, row, safeCol, safeRow, mmult,- )+ ) where +import Control.Category (id) import Data.Distributive (Distributive (..)) import Data.Functor.Rep import Data.List ((!!))@@ -92,13 +90,14 @@ -- >>> :set -XOverloadedLists -- >>> :set -XTypeFamilies -- >>> :set -XFlexibleContexts--- >>> let s = [1] :: Array ('[] :: [Nat]) Int+-- >>> let s = [1] :: Array ('[] :: [Nat]) Int -- scalar+-- >>> let v = [1,2,3] :: Array '[3] Int -- vector+-- >>> let m = [0..11] :: Array '[3,4] Int -- matrix -- >>> let a = [1..24] :: Array '[2,3,4] Int--- >>> let v = [1,2,3] :: Array '[3] Int--- >>> let m = [0..11] :: Array '[3,4] Int -- | a multidimensional array with a type-level shape --+-- >>> let a = [1..24] :: Array '[2,3,4] Int -- >>> a -- [[[1, 2, 3, 4], -- [5, 6, 7, 8],@@ -106,6 +105,7 @@ -- [[13, 14, 15, 16], -- [17, 18, 19, 20], -- [21, 22, 23, 24]]]+-- -- >>> [1,2,3] :: Array '[2,2] Int -- [[*** Exception: NumHaskException {errorMessage = "shape mismatch"} newtype Array s a = Array {unArray :: V.Vector a} deriving (Eq, Ord, NFData, Functor, Foldable, Generic, Traversable)@@ -203,7 +203,6 @@ nearZero (Array a) = all nearZero a --- | from flat list instance ( HasShape s ) =>@@ -222,9 +221,7 @@ toList (Array v) = V.toList v --- * shape---- | get shape of an Array as a value+-- | Get shape of an Array as a value. -- -- >>> shape a -- [2,3,4]@@ -236,7 +233,7 @@ toDynamic :: (HasShape s) => Array s a -> D.Array a toDynamic a = D.fromFlatList (shape a) (P.toList a) --- | use a dynamic array in a fixed context+-- | Use a dynamic array in a fixed context. -- -- >>> with (D.fromFlatList [2,3,4] [1..24]) (selects (Proxy :: Proxy '[0,1]) [1,1] :: Array '[2,3,4] Int -> Array '[4] Int) -- [17, 18, 19, 20]@@ -248,9 +245,7 @@ r with (D.Array _ v) f = f (Array v) --- * operations---- | reshape an array (with the same number of elements)+-- | Reshape an array (with the same number of elements). -- -- >>> reshape a :: Array '[4,3,2] Int -- [[[1, 2],@@ -278,14 +273,14 @@ s = shapeVal (toShape @s) s' = shapeVal (toShape @s') --- | reverse indices eg transposes the element /Aijk/ to /Akji/+-- | Reverse indices eg transposes the element A/ijk/ to A/kji/. -- -- >>> index (transpose a) [1,0,0] == index a [0,0,1] -- True transpose :: forall a s. (HasShape s, HasShape (Reverse s)) => Array s a -> Array (Reverse s) a transpose a = tabulate (index a . reverse) --- |+-- | The identity array. -- -- >>> ident :: Array '[3,2] Int -- [[1, 0],@@ -299,7 +294,8 @@ isDiag [x, y] = x == y isDiag (x : y : xs) = x == y && isDiag (y : xs) --- |+-- | Extract the diagonal of an array.+-- -- >>> diag (ident :: Array '[3,2] Int) -- [1, 1] diag ::@@ -315,7 +311,8 @@ go (s' : _) = index a (replicate (length ds) s') ds = shapeVal (toShape @s) --- |+-- | Create an array composed of a single value.+-- -- >>> singleton one :: Array '[3,2] Int -- [[1, 1], -- [1, 1],@@ -323,7 +320,7 @@ singleton :: (HasShape s) => a -> Array s a singleton a = tabulate (const a) --- | /selects ds ps a/ select from /a/ elements /ds/ dimensions at positions /ps/+-- | Select an array along dimensions. -- -- >>> let s = selects (Proxy :: Proxy '[0,1]) [1,1] a -- >>> :t s@@ -347,7 +344,7 @@ go s = index a (addIndexes s ds i) ds = shapeVal (toShape @ds) --- | select an index /except/ along dimensions+-- | Select an index /except/ along specified dimensions. -- -- >>> let s = selectsExcept (Proxy :: Proxy '[2]) [1,1] a -- >>> :t s@@ -371,7 +368,7 @@ go s = index a (addIndexes i ds s) ds = shapeVal (toShape @ds) --- | fold along specified dimensions+-- | Fold along specified dimensions. -- -- >>> folds sum (Proxy :: Proxy '[1]) a -- [68, 100, 132]@@ -392,7 +389,7 @@ where go s = f (selects d s a) --- | extracts dimensions to an outer layer+-- | Extracts dimensions to an outer layer. -- -- >>> let e = extracts (Proxy :: Proxy '[1,2]) a -- >>> :t e@@ -413,7 +410,7 @@ where go s = selects d s a --- | extracts /except/ dimensions to an outer layer+-- | Extracts /except/ dimensions to an outer layer. -- -- >>> let e = extractsExcept (Proxy :: Proxy '[1,2]) a -- >>> :t e@@ -434,7 +431,7 @@ where go s = selectsExcept d s a --- | join inner and outer dimension layers+-- | Join inner and outer dimension layers. -- -- >>> let e = extracts (Proxy :: Proxy '[1,0]) a --@@ -464,7 +461,7 @@ go s = index (index a (takeIndexes s ds)) (dropIndexes s ds) ds = shapeVal (toShape @ds) --- | maps along specified dimensions+-- | Maps a function along specified dimensions. -- -- >>> :t maps (transpose) (Proxy :: Proxy '[1]) a -- maps (transpose) (Proxy :: Proxy '[1]) a :: Array '[4, 3, 2] Int@@ -487,7 +484,7 @@ Array st' b maps f d a = joins d (fmapRep f (extracts d a)) --- | concatenate along a dimension+-- | Concatenate along a dimension. -- -- >>> :t concatenate (Proxy :: Proxy 1) a a -- concatenate (Proxy :: Proxy 1) a a :: Array '[2, 6, 4] Int@@ -521,7 +518,7 @@ ds0 = shapeVal (toShape @s0) d = fromIntegral $ natVal @d Proxy --- | /insert (Proxy :: Proxy d) (Proxy :: Proxy i)/ insert along the dimension /d/ at position /i/+-- | Insert along a dimension at a position. -- -- >>> insert (Proxy :: Proxy 2) (Proxy :: Proxy 0) a ([100..105]) -- [[[100, 1, 2, 3, 4],@@ -554,7 +551,7 @@ d = fromIntegral $ natVal @d Proxy i = fromIntegral $ natVal @i Proxy --- | insert along a dimension at the end+-- | Insert along a dimension at the end. -- -- >>> :t append (Proxy :: Proxy 0) a -- append (Proxy :: Proxy 0) a@@ -575,7 +572,7 @@ Array (Insert d s) a append d = insert d (Proxy :: Proxy (Dimension s d - 1)) --- | change the order of dimensions+-- | Change the order of dimensions. -- -- >>> let r = reorder (Proxy :: Proxy '[2,0,1]) a -- >>> :t r@@ -595,13 +592,15 @@ go s = index a (addIndexes [] ds s) ds = shapeVal (toShape @ds) --- | product two arrays using the supplied binary function--- If the function is multiply, and the arrays are tensors,+-- | Product two arrays using the supplied binary function.+--+-- For context, if the function is multiply, and the arrays are tensors, -- then this can be interpreted as a tensor product. -- -- https://en.wikipedia.org/wiki/Tensor_product -- -- The concept of a tensor product is a dense crossroad, and a complete treatment is elsewhere. To quote:+-- -- ... the tensor product can be extended to other categories of mathematical objects in addition to vector spaces, such as to matrices, tensors, algebras, topological vector spaces, and modules. In each such case the tensor product is characterized by a similar universal property: it is the freest bilinear operation. The general concept of a "tensor product" is captured by monoidal categories; that is, the class of all things that have a tensor product is a monoidal category. -- -- >>> expand (*) v v@@ -622,9 +621,9 @@ where r = rank (shape a) --- | contract an array by applying the supplied (folding) function on diagonal elements of the dimensions.+-- | Contract an array by applying the supplied (folding) function on diagonal elements of the dimensions. ----- This generalises a tensor contraction by allowing the number of contracting diagonals to be other than 2, and allowing another binary other than addition+-- This generalises a tensor contraction by allowing the number of contracting diagonals to be other than 2, and allowing a binary operator other than multiplication. -- -- >>> let b = [1..6] :: Array '[2,3] Int -- >>> contract sum (Proxy :: Proxy '[1,2]) (expand (*) b (transpose b))@@ -647,16 +646,16 @@ Array s' b contract f xs a = f . diag <$> extractsExcept xs a --- | a generalisation of a dot operation, which is a multiplicative expansion of two arrays and sum contraction along the middle two dimensions.+-- | A generalisation of a dot operation, which is a multiplicative expansion of two arrays and sum contraction along the middle two dimensions. ----- dot sum (*) on two matrices is known as matrix multiplication+-- matrix multiplication -- -- >>> let b = [1..6] :: Array '[2,3] Int -- >>> dot sum (*) b (transpose b) -- [[14, 32], -- [32, 77]] ----- dot sum (*) on two vectors is known as the inner product+-- inner product -- -- >>> let v = [1..3] :: Array '[3] Int -- >>> :t dot sum (*) v v@@ -665,14 +664,38 @@ -- >>> dot sum (*) v v -- 14 ----- dot sum (*) m v on a matrix and a vector is matrix-vector multiplication--- Note that an `Array '[3] Int` is neither a row vector nor column vector. `dot` is not turning the vector into a matrix and then using matrix multiplication.+-- matrix-vector multiplication+-- (Note how the vector doesn't need to be converted to a row or column vector) ----- >>> dot sum (*) v b+-- >>> dot sum (*) v b -- [9, 12, 15] -- -- >>> dot sum (*) b v -- [14, 32]+--+-- dot allows operation on mis-shaped matrices:+--+-- >>> let m23 = [1..6] :: Array '[2,3] Int+-- >>> let m12 = [1,2] :: Array '[1,2] Int+-- >>> shape $ dot sum (*) m23 m12+-- [2,2]+--+-- the algorithm ignores excess positions within the contracting dimension(s):+--+-- m23 shape: 2 3+--+-- m12 shape: 1 2+--+-- res shape: 2 2+--+-- FIXME: work out whether this is a feature or a bug...+--+-- find instances of a vector in a matrix+--+-- >>> let cs = fromList ("abacbaab" :: [Char]) :: Array '[4,2] Char+-- >>> let v = fromList ("ab" :: [Char]) :: Vector 2 Char+-- >>> dot (all id) (==) cs v+-- [True, False, False, True] dot :: forall a b c d sa sb s' ss se. ( HasShape sa,@@ -695,8 +718,55 @@ Array s' d dot f g a b = contract f (Proxy :: Proxy '[Rank sa - 1, Rank sa]) (expand g a b) --- | select elements along every dimension+-- | Array multiplication. --+-- matrix multiplication+--+-- >>> let b = [1..6] :: Array '[2,3] Int+-- >>> mult b (transpose b)+-- [[14, 32],+-- [32, 77]]+--+-- inner product+--+-- >>> let v = [1..3] :: Array '[3] Int+-- >>> :t mult v v+-- mult v v :: Array '[] Int+--+-- >>> mult v v+-- 14+--+-- matrix-vector multiplication+--+-- >>> mult v b+-- [9, 12, 15]+--+-- >>> mult b v+-- [14, 32]+mult ::+ forall a sa sb s' ss se.+ ( Additive a,+ Multiplicative a,+ HasShape sa,+ HasShape sb,+ HasShape (sa ++ sb),+ se ~ TakeIndexes (sa ++ sb) '[Rank sa - 1, Rank sa],+ HasShape se,+ KnownNat (Minimum se),+ KnownNat (Rank sa - 1),+ KnownNat (Rank sa),+ ss ~ '[Minimum se],+ HasShape ss,+ s' ~ DropIndexes (sa ++ sb) '[Rank sa - 1, Rank sa],+ HasShape s'+ ) =>+ Array sa a ->+ Array sb a ->+ Array s' a+mult = dot sum (*)++-- | Select elements along positions in every dimension.+-- -- >>> let s = slice (Proxy :: Proxy '[[0,1],[0,2],[1,2]]) a -- >>> :t s -- s :: Array '[2, 2, 2] Int@@ -729,7 +799,7 @@ go s = index a (zipWith (!!) pss' s) pss' = natValss pss --- | remove singleton dimensions+-- | Remove single dimensions. -- -- >>> let a = [1..24] :: Array '[2,1,3,4,1] Int -- >>> a@@ -779,10 +849,10 @@ -- | <https://en.wikipedia.org/wiki/Scalarr_(mathematics) Wiki Scalar> ----- An /Array '[] a/ despite being a Scalar is never-the-less a one-element vector under the hood. Unification of representation is unexplored.+-- An Array '[] a despite being a Scalar is never-the-less a one-element vector under the hood. Unification of representation is unexplored. type Scalar a = Array ('[] :: [Nat]) a --- | unwrapping scalars is probably a performance bottleneck+-- | Unwrapping scalars is probably a performance bottleneck. -- -- >>> let s = [3] :: Array ('[] :: [Nat]) Int -- >>> fromScalar s@@ -790,22 +860,16 @@ fromScalar :: (HasShape ('[] :: [Nat])) => Array ('[] :: [Nat]) a -> a fromScalar a = index a ([] :: [Int]) --- | convert a number to a scalar+-- | Convert a number to a scalar. -- -- >>> :t toScalar 2 -- toScalar 2 :: Num a => Array '[] a toScalar :: (HasShape ('[] :: [Nat])) => a -> Array ('[] :: [Nat]) a toScalar a = fromList [a] --- $vector--- Vector specialisations- -- | <https://en.wikipedia.org/wiki/Vector_(mathematics_and_physics) Wiki Vector> type Vector s a = Array '[s] a --- $matrix--- Matrix specialisations- -- | <https://en.wikipedia.org/wiki/Matrix_(mathematics) Wiki Matrix> type Matrix m n a = Array '[m, n] a @@ -823,7 +887,7 @@ one = ident --- | extract specialised to a matrix+-- | Extract specialised to a matrix. -- -- >>> row 1 m -- [4, 5, 6, 7]@@ -832,7 +896,7 @@ where n = fromIntegral $ natVal @n Proxy --- | row extraction checked at type level+-- | Row extraction checked at type level. -- -- >>> safeRow (Proxy :: Proxy 1) m -- [4, 5, 6, 7]@@ -847,7 +911,7 @@ n = fromIntegral $ natVal @n Proxy j = fromIntegral $ natVal @j Proxy --- | extract specialised to a matrix+-- | Extract specialised to a matrix. -- -- >>> col 1 m -- [1, 5, 9]@@ -857,7 +921,7 @@ m = fromIntegral $ natVal @m Proxy n = fromIntegral $ natVal @n Proxy --- | column extraction checked at type level+-- | Column extraction checked at type level. -- -- >>> safeCol (Proxy :: Proxy 1) m -- [1, 5, 9]@@ -873,7 +937,7 @@ n = fromIntegral $ natVal @n Proxy j = fromIntegral $ natVal @j Proxy --- | matrix multiplication+-- | Matrix multiplication. -- -- This is dot sum (*) specialised to matrices --
src/NumHask/Array/HMatrix.hs view
@@ -15,39 +15,104 @@ {-# LANGUAGE TypeOperators #-} {-# OPTIONS_GHC -Wno-redundant-constraints #-} {-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-}-{-# OPTIONS_GHC -fno-warn-missing-signatures #-} +-- | Arrays with a fixed shape, with a HMatrix representation. module NumHask.Array.HMatrix- ( Array(..),+ ( -- $setup+ Array (..),++ -- * Representation+ --+ -- With no functor instance, we instead supply the representable API index,+ tabulate,++ -- * Conversion+ shape,+ toDynamic,+ toFixed,+ fromFixed,++ -- * Operators+ reshape,+ transpose,+ diag,+ ident,+ singleton,+ selects,+ selectsExcept,+ folds,+ concatenate,+ insert,+ append,+ reorder,+ expand,+ slice,+ squeeze,++ -- * Scalar+ --+ -- Scalar specialisations+ Scalar,+ fromScalar,+ toScalar,++ -- * Vector+ --+ -- Vector specialisations.+ Vector,++ -- * Matrix+ --+ -- Matrix specialisations.+ Matrix,+ col,+ row,+ safeCol,+ safeRow, mmult,- ) where+ )+where +import Data.List ((!!))+import qualified Data.Vector as V import GHC.Exts (IsList (..)) import GHC.Show (Show (..))+import GHC.TypeLits+import qualified NumHask.Array.Dynamic as D+import qualified NumHask.Array.Fixed as F import NumHask.Array.Shape-import NumHask.Prelude as P+import NumHask.Prelude as P hiding (transpose) import qualified Numeric.LinearAlgebra as H import qualified Numeric.LinearAlgebra.Devel as H import qualified Prelude +-- $setup+-- >>> :set -XDataKinds+-- >>> :set -XOverloadedLists+-- >>> :set -XTypeFamilies+-- >>> :set -XFlexibleContexts+-- >>> let s = [1] :: Array ('[] :: [Nat]) Int -- scalar+-- >>> let v = [1,2,3] :: Array '[3] Int -- vector+-- >>> let m = [0..11] :: Array '[3,4] Int -- matrix+-- >>> let a = [1..24] :: Array '[2,3,4] Int++-- | a multidimensional array with a type-level shape+--+-- >>> let a = [1..24] :: Array '[2,3,4] Int+-- >>> a+-- [[[1, 2, 3, 4],+-- [5, 6, 7, 8],+-- [9, 10, 11, 12]],+-- [[13, 14, 15, 16],+-- [17, 18, 19, 20],+-- [21, 22, 23, 24]]]+--+-- >>> [1,2,3] :: Array '[2,2] Int+-- [[*** Exception: NumHaskException {errorMessage = "shape mismatch"} newtype Array s a = Array {unArray :: H.Matrix a} deriving (Show, NFData, Generic) --- | explicit rather than via Representable-index ::- forall s a.- ( HasShape s,- H.Element a,- H.Container H.Vector a- ) =>- Array s a ->- [Int] ->- a-index (Array v) i = H.flatten v `H.atIndex` flatten s i- where- s = shapeVal (toShape @s)- instance ( Additive a, HasShape s,@@ -112,10 +177,11 @@ (*.) s (Array r) = Array $ H.cmap (s *) r {-# INLINE (*.) #-} +type instance Actor (Array s a) = a+ -- | from flat list instance ( HasShape s,- Additive a, H.Element a ) => IsList (Array s a)@@ -123,15 +189,561 @@ type Item (Array s a) = a - fromList l = Array $ H.reshape n $ H.fromList $ take mn $ l ++ repeat zero+ fromList l =+ bool+ (throw (NumHaskException "shape mismatch"))+ (Array $ H.reshape n $ H.fromList l)+ ((length l == 1 && null s) || (length l == size s)) where- mn = P.product $ shapeVal (toShape @s) s = shapeVal (toShape @s) n = Prelude.last s toList (Array v) = H.toList $ H.flatten v --- | fast+-- | Get shape of an Array as a value.+--+-- >>> shape a+-- [2,3,4]+shape :: forall a s. (HasShape s) => Array s a -> [Int]+shape _ = shapeVal $ toShape @s+{-# INLINE shape #-}++-- | Convert to a dynamic array.+toDynamic :: (HasShape s, H.Element a) => Array s a -> D.Array a+toDynamic a@(Array h) = D.fromFlatList (shape a) (mconcat $ H.toLists h)++-- | Convert to a fixed array.+toFixed :: (HasShape s, H.Element a) => Array s a -> F.Array s a+toFixed (Array h) = fromList (mconcat $ H.toLists h)++-- | Convert from a fixed array.+fromFixed :: (HasShape s, H.Element a) => F.Array s a -> Array s a+fromFixed a = fromList (P.toList a)++-- | with no fmap, we supply the representable API+index ::+ forall s a.+ ( HasShape s,+ H.Element a,+ H.Container H.Vector a+ ) =>+ Array s a ->+ [Int] ->+ a+index (Array v) i = H.flatten v `H.atIndex` flatten s i+ where+ s = shapeVal (toShape @s)++-- | tabulate an array with a generating function+--+-- >>> tabulate [2,3,4] ((1+) . flatten [2,3,4]) == a+-- True+tabulate ::+ forall s a.+ ( HasShape s,+ H.Element a+ ) =>+ ([Int] -> a) ->+ Array s a+tabulate f =+ fromList (V.toList $ V.generate (size s) (f . shapen s))+ where+ s = shapeVal (toShape @s)++-- | Reshape an array (with the same number of elements).+--+-- >>> reshape a :: Array '[4,3,2] Int+-- [[[1, 2],+-- [3, 4],+-- [5, 6]],+-- [[7, 8],+-- [9, 10],+-- [11, 12]],+-- [[13, 14],+-- [15, 16],+-- [17, 18]],+-- [[19, 20],+-- [21, 22],+-- [23, 24]]]+reshape ::+ forall a s s'.+ ( Size s ~ Size s',+ HasShape s,+ HasShape s',+ H.Container H.Vector a+ ) =>+ Array s a ->+ Array s' a+reshape a = tabulate (index a . shapen s . flatten s')+ where+ s = shapeVal (toShape @s)+ s' = shapeVal (toShape @s')++-- | Reverse indices eg transposes the element A/ijk/ to A/kji/.+--+-- >>> index (transpose a) [1,0,0] == index a [0,0,1]+-- True+transpose :: forall a s. (H.Element a, H.Container H.Vector a, HasShape s, HasShape (Reverse s)) => Array s a -> Array (Reverse s) a+transpose a = tabulate (index a . reverse)++-- | The identity array.+--+-- >>> ident :: Array '[3,2] Int+-- [[1, 0],+-- [0, 1],+-- [0, 0]]+ident :: forall a s. (H.Element a, H.Container H.Vector a, HasShape s, Additive a, Multiplicative a) => Array s a+ident = tabulate (bool zero one . isDiag)+ where+ isDiag [] = True+ isDiag [_] = True+ isDiag [x, y] = x == y+ isDiag (x : y : xs) = x == y && isDiag (y : xs)++-- | Extract the diagonal of an array.+--+-- >>> diag (ident :: Array '[3,2] Int)+-- [1, 1]+diag ::+ forall a s.+ ( HasShape s,+ HasShape '[Minimum s],+ H.Element a,+ H.Container H.Vector a+ ) =>+ Array s a ->+ Array '[Minimum s] a+diag a = tabulate go+ where+ go [] = throw (NumHaskException "Rank Underflow")+ go (s' : _) = index a (replicate (length ds) s')+ ds = shapeVal (toShape @s)++-- | Create an array composed of a single value.+--+-- >>> singleton one :: Array '[3,2] Int+-- [[1, 1],+-- [1, 1],+-- [1, 1]]+singleton :: (H.Element a, H.Container H.Vector a, HasShape s) => a -> Array s a+singleton a = tabulate (const a)++-- | Select an array along dimensions.+--+-- >>> let s = selects (Proxy :: Proxy '[0,1]) [1,1] a+-- >>> :t s+-- s :: Array '[4] Int+--+-- >>> s+-- [17, 18, 19, 20]+selects ::+ forall ds s s' a.+ ( HasShape s,+ HasShape ds,+ HasShape s',+ s' ~ DropIndexes s ds,+ H.Element a,+ H.Container H.Vector a+ ) =>+ Proxy ds ->+ [Int] ->+ Array s a ->+ Array s' a+selects _ i a = tabulate go+ where+ go s = index a (addIndexes s ds i)+ ds = shapeVal (toShape @ds)++-- | Select an index /except/ along specified dimensions.+--+-- >>> let s = selectsExcept (Proxy :: Proxy '[2]) [1,1] a+-- >>> :t s+-- s :: Array '[4] Int+--+-- >>> s+-- [17, 18, 19, 20]+selectsExcept ::+ forall ds s s' a.+ ( HasShape s,+ HasShape ds,+ HasShape s',+ s' ~ TakeIndexes s ds,+ H.Element a,+ H.Container H.Vector a+ ) =>+ Proxy ds ->+ [Int] ->+ Array s a ->+ Array s' a+selectsExcept _ i a = tabulate go+ where+ go s = index a (addIndexes i ds s)+ ds = shapeVal (toShape @ds)++-- | Fold along specified dimensions.+--+-- >>> folds sum (Proxy :: Proxy '[1]) a+-- [68, 100, 132]+folds ::+ forall ds st si so a b.+ ( HasShape st,+ HasShape ds,+ HasShape si,+ HasShape so,+ si ~ DropIndexes st ds,+ so ~ TakeIndexes st ds,+ H.Element a,+ H.Container H.Vector a,+ H.Element b,+ H.Container H.Vector b+ ) =>+ (Array si a -> b) ->+ Proxy ds ->+ Array st a ->+ Array so b+folds f d a = tabulate go+ where+ go s = f (selects d s a)++-- | Concatenate along a dimension.+--+-- >>> :t concatenate (Proxy :: Proxy 1) a a+-- concatenate (Proxy :: Proxy 1) a a :: Array '[2, 6, 4] Int+concatenate ::+ forall a s0 s1 d s.+ ( CheckConcatenate d s0 s1 s,+ Concatenate d s0 s1 ~ s,+ HasShape s0,+ HasShape s1,+ HasShape s,+ KnownNat d,+ H.Element a,+ H.Container H.Vector a+ ) =>+ Proxy d ->+ Array s0 a ->+ Array s1 a ->+ Array s a+concatenate _ s0 s1 = tabulate go+ where+ go s =+ bool+ (index s0 s)+ ( index+ s1+ ( addIndex+ (dropIndex s d)+ d+ ((s !! d) - (ds0 !! d))+ )+ )+ ((s !! d) >= (ds0 !! d))+ ds0 = shapeVal (toShape @s0)+ d = fromIntegral $ natVal @d Proxy++-- | Insert along a dimension at a position.+--+-- >>> insert (Proxy :: Proxy 2) (Proxy :: Proxy 0) a ([100..105])+-- [[[100, 1, 2, 3, 4],+-- [101, 5, 6, 7, 8],+-- [102, 9, 10, 11, 12]],+-- [[103, 13, 14, 15, 16],+-- [104, 17, 18, 19, 20],+-- [105, 21, 22, 23, 24]]]+insert ::+ forall a s s' d i.+ ( DropIndex s d ~ s',+ CheckInsert d i s,+ KnownNat i,+ KnownNat d,+ HasShape s,+ HasShape s',+ HasShape (Insert d s),+ H.Element a,+ H.Container H.Vector a+ ) =>+ Proxy d ->+ Proxy i ->+ Array s a ->+ Array s' a ->+ Array (Insert d s) a+insert _ _ a b = tabulate go+ where+ go s+ | s !! d == i = index b (dropIndex s d)+ | s !! d < i = index a s+ | otherwise = index a (decAt d s)+ d = fromIntegral $ natVal @d Proxy+ i = fromIntegral $ natVal @i Proxy++-- | Insert along a dimension at the end.+--+-- >>> :t append (Proxy :: Proxy 0) a+-- append (Proxy :: Proxy 0) a+-- :: Array '[3, 4] Int -> Array '[3, 3, 4] Int+append ::+ forall a d s s'.+ ( DropIndex s d ~ s',+ CheckInsert d (Dimension s d - 1) s,+ KnownNat (Dimension s d - 1),+ KnownNat d,+ HasShape s,+ HasShape s',+ HasShape (Insert d s),+ H.Element a,+ H.Container H.Vector a+ ) =>+ Proxy d ->+ Array s a ->+ Array s' a ->+ Array (Insert d s) a+append d = insert d (Proxy :: Proxy (Dimension s d - 1))++-- | Change the order of dimensions.+--+-- >>> let r = reorder (Proxy :: Proxy '[2,0,1]) a+-- >>> :t r+-- r :: Array '[4, 2, 3] Int+reorder ::+ forall a ds s.+ ( HasShape ds,+ HasShape s,+ HasShape (Reorder s ds),+ CheckReorder ds s,+ H.Element a,+ H.Container H.Vector a+ ) =>+ Proxy ds ->+ Array s a ->+ Array (Reorder s ds) a+reorder _ a = tabulate go+ where+ go s = index a (addIndexes [] ds s)+ ds = shapeVal (toShape @ds)++-- | Product two arrays using the supplied binary function.+--+-- For context, if the function is multiply, and the arrays are tensors,+-- then this can be interpreted as a tensor product.+--+-- https://en.wikipedia.org/wiki/Tensor_product+--+-- The concept of a tensor product is a dense crossroad, and a complete treatment is elsewhere. To quote:+--+-- ... the tensor product can be extended to other categories of mathematical objects in addition to vector spaces, such as to matrices, tensors, algebras, topological vector spaces, and modules. In each such case the tensor product is characterized by a similar universal property: it is the freest bilinear operation. The general concept of a "tensor product" is captured by monoidal categories; that is, the class of all things that have a tensor product is a monoidal category.+--+-- >>> expand (*) v v+-- [[1, 2, 3],+-- [2, 4, 6],+-- [3, 6, 9]]+expand ::+ forall s s' a b c.+ ( HasShape s,+ HasShape s',+ HasShape ((++) s s'),+ H.Element a,+ H.Container H.Vector a,+ H.Element b,+ H.Container H.Vector b,+ H.Element c+ ) =>+ (a -> b -> c) ->+ Array s a ->+ Array s' b ->+ Array ((++) s s') c+expand f a b = tabulate (\i -> f (index a (take r i)) (index b (drop r i)))+ where+ r = rank (shape a)++-- | Select elements along positions in every dimension.+--+-- >>> let s = slice (Proxy :: Proxy '[[0,1],[0,2],[1,2]]) a+-- >>> :t s+-- s :: Array '[2, 2, 2] Int+--+-- >>> s+-- [[[2, 3],+-- [10, 11]],+-- [[14, 15],+-- [22, 23]]]+--+-- >>> let s = squeeze $ slice (Proxy :: Proxy '[ '[0], '[0], '[0]]) a+-- >>> :t s+-- s :: Array '[] Int+--+-- >>> s+-- 1+slice ::+ forall (pss :: [[Nat]]) s s' a.+ ( HasShape s,+ HasShape s',+ KnownNatss pss,+ KnownNat (Rank pss),+ s' ~ Ranks pss,+ H.Element a,+ H.Container H.Vector a+ ) =>+ Proxy pss ->+ Array s a ->+ Array s' a+slice pss a = tabulate go+ where+ go s = index a (zipWith (!!) pss' s)+ pss' = natValss pss++-- | Remove single dimensions.+--+-- >>> let a = [1..24] :: Array '[2,1,3,4,1] Int+-- >>> a+-- [[[[[1],+-- [2],+-- [3],+-- [4]],+-- [[5],+-- [6],+-- [7],+-- [8]],+-- [[9],+-- [10],+-- [11],+-- [12]]]],+-- [[[[13],+-- [14],+-- [15],+-- [16]],+-- [[17],+-- [18],+-- [19],+-- [20]],+-- [[21],+-- [22],+-- [23],+-- [24]]]]]+-- >>> squeeze a+-- [[[1, 2, 3, 4],+-- [5, 6, 7, 8],+-- [9, 10, 11, 12]],+-- [[13, 14, 15, 16],+-- [17, 18, 19, 20],+-- [21, 22, 23, 24]]]+--+-- >>> squeeze ([1] :: Array '[1,1] Double)+-- 1.0+squeeze ::+ forall s t a.+ (t ~ Squeeze s) =>+ Array s a ->+ Array t a+squeeze (Array x) = Array x++-- $scalar+-- Scalar specialisations++-- | <https://en.wikipedia.org/wiki/Scalarr_(mathematics) Wiki Scalar>+--+-- An Array '[] a despite being a Scalar is never-the-less a one-element vector under the hood. Unification of representation is unexplored.+type Scalar a = Array ('[] :: [Nat]) a++-- | Unwrapping scalars is probably a performance bottleneck.+--+-- >>> let s = [3] :: Array ('[] :: [Nat]) Int+-- >>> fromScalar s+-- 3+fromScalar :: (H.Element a, H.Container H.Vector a, HasShape ('[] :: [Nat])) => Array ('[] :: [Nat]) a -> a+fromScalar a = index a ([] :: [Int])++-- | Convert a number to a scalar.+--+-- >>> :t toScalar 2+-- toScalar 2 :: Num a => Array '[] a+toScalar :: (H.Element a, H.Container H.Vector a, HasShape ('[] :: [Nat])) => a -> Array ('[] :: [Nat]) a+toScalar a = fromList [a]++-- | <https://en.wikipedia.org/wiki/Vector_(mathematics_and_physics) Wiki Vector>+type Vector s a = Array '[s] a++-- | <https://en.wikipedia.org/wiki/Matrix_(mathematics) Wiki Matrix>+type Matrix m n a = Array '[m, n] a++instance+ ( Multiplicative a,+ P.Distributive a,+ Subtractive a,+ H.Numeric a,+ KnownNat m,+ HasShape '[m, m],+ H.Element a,+ H.Container H.Vector a+ ) =>+ Multiplicative (Matrix m m a)+ where++ (*) = mmult++ one = ident++-- | Extract specialised to a matrix.+--+-- >>> row 1 m+-- [4, 5, 6, 7]+row :: forall m n a. (H.Element a, H.Container H.Vector a, KnownNat m, KnownNat n, HasShape '[m, n]) => Int -> Matrix m n a -> Vector n a+row i (Array a) = fromList $ H.toList $ H.subVector (i * n) n (H.flatten a)+ where+ n = fromIntegral $ natVal @n Proxy++-- | Row extraction checked at type level.+--+-- >>> safeRow (Proxy :: Proxy 1) m+-- [4, 5, 6, 7]+--+-- >>> safeRow (Proxy :: Proxy 3) m+-- ...+-- ... index outside range+-- ...+safeRow :: forall m n a j. (H.Element a, H.Container H.Vector a, 'True ~ CheckIndex j m, KnownNat j, KnownNat m, KnownNat n, HasShape '[m, n]) => Proxy j -> Matrix m n a -> Vector n a+safeRow _j (Array a) = fromList $ H.toList $ H.subVector (j * n) n (H.flatten a)+ where+ n = fromIntegral $ natVal @n Proxy+ j = fromIntegral $ natVal @j Proxy++-- | Extract specialised to a matrix.+--+-- >>> col 1 m+-- [1, 5, 9]+col :: forall m n a. (H.Element a, H.Container H.Vector a, KnownNat m, KnownNat n, HasShape '[m, n]) => Int -> Matrix m n a -> Vector n a+col i (Array a) = Array $ H.takeColumns i a++-- | Column extraction checked at type level.+--+-- >>> safeCol (Proxy :: Proxy 1) m+-- [1, 5, 9]+--+-- >>> safeCol (Proxy :: Proxy 4) m+-- ...+-- ... index outside range+-- ...+safeCol :: forall m n a j. (H.Element a, H.Container H.Vector a, 'True ~ CheckIndex j n, KnownNat j, KnownNat m, KnownNat n, HasShape '[m, n]) => Proxy j -> Matrix m n a -> Vector n a+safeCol _j (Array a) = Array $ H.takeColumns j a+ where+ j = fromIntegral $ natVal @j Proxy++-- | Matrix multiplication.+--+-- This is dot sum (*) specialised to matrices+--+-- >>> let a = [1, 2, 3, 4] :: Array '[2, 2] Int+-- >>> let b = [5, 6, 7, 8] :: Array '[2, 2] Int+-- >>> a+-- [[1, 2],+-- [3, 4]]+--+-- >>> b+-- [[5, 6],+-- [7, 8]]+--+-- >>> mmult a b+-- [[19, 22],+-- [43, 50]] mmult :: forall m n k a. ( KnownNat k,@@ -145,5 +757,3 @@ Array [k, n] a -> Array [m, n] a mmult (Array x) (Array y) = Array $ x H.<> y--type instance Actor (Array s a) = a
src/NumHask/Array/Shape.hs view
@@ -17,7 +17,64 @@ {-# OPTIONS_GHC -Wall #-} -- | Functions for manipulating shape. The module tends to supply equivalent functionality at type-level and value-level with functions of the same name (except for capitalization).-module NumHask.Array.Shape where+module NumHask.Array.Shape+ ( Shape (..),+ HasShape (..),+ type (++),+ type (!!),+ Take,+ Drop,+ Reverse,+ ReverseGo,+ Filter,+ rank,+ Rank,+ ranks,+ Ranks,+ size,+ Size,+ dimension,+ Dimension,+ flatten,+ shapen,+ minimum,+ Minimum,+ checkIndex,+ CheckIndex,+ checkIndexes,+ CheckIndexes,+ addIndex,+ AddIndex,+ dropIndex,+ DropIndex,+ posRelative,+ PosRelative,+ PosRelativeGo,+ addIndexes,+ AddIndexes,+ AddIndexesGo,+ dropIndexes,+ DropIndexes,+ takeIndexes,+ TakeIndexes,+ exclude,+ Exclude,+ concatenate',+ Concatenate,+ CheckConcatenate,+ Insert,+ CheckInsert,+ reorder',+ Reorder,+ CheckReorder,+ squeeze',+ Squeeze,+ incAt,+ decAt,+ KnownNats (..),+ KnownNatss (..),+ )+where import Data.List ((!!)) import Data.Type.Bool@@ -25,6 +82,7 @@ import NumHask.Prelude as P hiding (Last, minimum) -- | The Shape type holds a [Nat] at type level and the equivalent [Int] at value level.+-- Using [Int] as the index for an array nicely represents the practical interests and constraints downstream of this high-level API: densely-packed numbers (reals or integrals), indexed and layered. newtype Shape (s :: [Nat]) = Shape {shapeVal :: [Int]} deriving (Show) class HasShape s where@@ -285,6 +343,10 @@ type family Exclude (r :: Nat) (i :: [Nat]) where Exclude r i = DropIndexes (EnumerateGo r) i +-- | concatenate+--+-- >>> concatenate' 1 [2,3,4] [2,3,4]+-- [2,6,4] concatenate' :: Int -> [Int] -> [Int] -> [Int] concatenate' i s0 s1 = take i s0 ++ (dimension s0 i + dimension s1 i : drop (i + 1) s0) @@ -310,7 +372,7 @@ decAt :: Int -> [Int] -> [Int] decAt d s = take d s ++ (dimension s d - 1 : drop (d + 1) s) --- /reorder' s i/ reorders the dimensions of shape /s/ according to a list of positions /i/+-- | /reorder' s i/ reorders the dimensions of shape /s/ according to a list of positions /i/ -- -- >>> reorder' [2,3,4] [2,0,1] -- [4,2,3]@@ -334,8 +396,9 @@ (L.TypeError ('Text "bad dimensions")) ~ 'True +-- | remove 1's from a list squeeze' :: (Eq a, Num a) => [a] -> [a]-squeeze' = filter (/=1)+squeeze' = filter (/= 1) type family Squeeze (a :: [Nat]) where Squeeze '[] = '[]
+ stack.yaml view
@@ -0,0 +1,11 @@+resolver: lts-14.13++packages:+ - .++extra-deps:+ - numhask-0.3.1+ - numhask-prelude-0.3.2+ - numhask-hedgehog-0.3.1+ - numhask-space-0.3.0+ - tdigest-0.2.1