git.iko.soy/rowtype-yoga/purescript-linear-algebra
on master help sign in
9 files changed+471−41
since splitsnapshots
unifiedsplit
LICENSE+1−1 README.md+8−2 bower.json+7−5 src/LinearAlgebra/Matrix.purs+171−12 src/LinearAlgebra/Vector.purs+65−1 test/Main.purs+7−6 test/Test/LinearAlgebra/Matrix.purs+125−14 test/Test/LinearAlgebra/Vector.purs+59 test/Test/PerfTest.purs+28
LICENSE+1−1
@@ -186,7 +186,7 @@
186186 same "printed page" as the copyright notice for easier
187187 identification within third-party archives.
188188
189− Copyright {yyyy} {name of copyright owner}
189+ Copyright 2017 Krzysztof Langner
190190
191191 Licensed under the Apache License, Version 2.0 (the "License");
192192 you may not use this file except in compliance with the License.
README.md+8−2
@@ -21,10 +21,16 @@ bower install purescript-linear-algebra
2121
2222 * Matrix
2323 * [x] Matrix creation from Array
24− * [ ] Matrix addition
24+ * [x] Matrix multiplication
25+ * [x] Slice array
26+ * [x] Transpose
27+ * [x] Matrix addition
2528 * [ ] Multiply by scalar
2629 * Vector
27− * [ ] Create from array
30+ * [x] Dot product
31+ * [x] Multiply by scalar
32+ * [x] Add, differentiate
33+ * [x] ArgMax, ArgMin
2834
2935
3036## Documentation
bower.json+7−5
@@ -14,12 +14,14 @@
1414 "output"
1515 ],
1616 "dependencies": {
17− "purescript-prelude": "^3.0.0",
18− "purescript-arrays": "^4.0.1"
17+ "purescript-prelude": "^4.1.0",
18+ "purescript-arrays": "^5.0.0",
19+ "purescript-globals": "^4.0.0",
20+ "purescript-tuples": "^5.0.0"
1921 },
2022 "devDependencies": {
21− "purescript-assert": "^3.0.0",
22− "purescript-console": "^3.0.0",
23− "purescript-psci-support": "^3.0.0"
23+ "purescript-assert": "^4.0.0",
24+ "purescript-console": "^4.1.0",
25+ "purescript-psci-support": "^4.0.0"
2426 }
2527}
src/LinearAlgebra/Matrix.purs+171−12
@@ -1,35 +1,194 @@
11module LinearAlgebra.Matrix
2− ( Matrix
2+ ( Matrix
3+ -- * Constructors
34 , fromArray
5+ , fromColumn
6+ , fromRow
47 , replicate
58 , zeros
9+ , identity
10+ -- * Access data
11+ , ncols
12+ , nrows
13+ , column
14+ , element
15+ , row
16+ , columns
17+ , rows
18+ , sliceCols
19+ , sliceRows
20+ , toVector
21+ -- * Modify data
22+ , insertCol
23+ , transpose
24+ -- * Matrix operations
25+ , add
26+ , multiply
627 ) where
728
829import Prelude
930import Data.Array as A
10−import Data.Maybe (Maybe(..))
31+import Data.Maybe (Maybe(..), fromMaybe)
32+import LinearAlgebra.Vector (Vector)
33+import LinearAlgebra.Vector as V
1134
1235
1336-- | Dense Matrix implementation
14−type Matrix a =
15− { nrows :: Int
16− , ncols :: Int
17− , values :: Array a
18− }
37+data Matrix a = Dense Int Int (Array a) -- nrow, ncol, values
38+
39+instance eqMatrix :: Eq a => Eq (Matrix a) where
40+ eq (Dense r1 c1 d1) (Dense r2 c2 d2) = r1 == r2 && c1 == c2 && d1 == d2
41+
42+instance showMatrix :: Show a => Show (Matrix a) where
43+ show (Dense r c ds) = "Dense Matrix nrows=" <> show r
44+ <> ", ncols=" <> show c
45+ <> ", data=" <> show ds
46+
47+
48+-- | Number of rows in matrix
49+nrows :: ∀ a. Matrix a -> Int
50+nrows (Dense r _ _) = r
51+
52+-- | Number of cols in matrix
53+ncols :: ∀ a. Matrix a -> Int
54+ncols (Dense _ c _) = c
55+
56+-- | Convert Matrix to Vector
57+toVector :: ∀ a. Matrix a -> Vector a
58+toVector (Dense _ _ vs) = vs
1959
2060
2161-- | Create array of given dimmension containing replicated value
2262replicate :: ∀ a. Int -> Int -> a -> Maybe (Matrix a )
23−replicate r c v | r > 0 && c > 0 = Just {nrows: r, ncols: c, values: A.replicate (r * c) v}
63+replicate r c v | r > 0 && c > 0 = Just $ Dense r c (A.replicate (r * c) v)
2464 | otherwise = Nothing
2565
2666
2767-- | Create array of given dimmension with all values set to 0
28−zeros :: Int -> Int -> Maybe (Matrix Number)
29−zeros r c = replicate r c 0.0
68+zeros :: Int -> Int -> Matrix Number
69+zeros r c = Dense r' c' (A.replicate (r' * c') 0.0)
70+ where
71+ r' = if r > 0 then r else 1
72+ c' = if c > 0 then c else 1
73+
74+
75+-- | Create identy matrix
76+identity :: Int -> Matrix Number
77+identity n = Dense n n (identity' n)
78+
79+identity' :: Int -> Vector Number
80+identity' n = do
81+ r <- A.range 1 n
82+ c <- A.range 1 n
83+ pure $ if r == c then 1.0 else 0.0
3084
3185
3286-- | Create Matrix from Array
3387fromArray :: ∀ a. Int -> Int -> Array a -> Maybe (Matrix a)
34−fromArray r c vs | r > 0 && c > 0 && r*c == A.length vs = Just {nrows: r, ncols: c, values: vs}
35− | otherwise = Nothing
88+fromArray r c vs | r > 0 && c > 0 && r*c == A.length vs = Just (Dense r c vs)
89+ | otherwise = Nothing
90+
91+
92+-- | Create matrix from column
93+fromColumn :: ∀ a. Vector a -> Matrix a
94+fromColumn vs = Dense (A.length vs) 1 vs
95+
96+
97+-- | Create matrix from row
98+fromRow :: ∀ a. Vector a -> Matrix a
99+fromRow vs = Dense 1 (A.length vs) vs
100+
101+
102+-- | Get specific column as a vector. Index is 0 based
103+-- | If the index is out of range then return empty vector
104+column :: ∀ a. Int -> Matrix a -> Vector a
105+column c (Dense nr nc vs) = A.mapMaybe (\i -> A.index vs (i*nc+c)) (A.range 0 (nr-1))
106+
107+
108+-- | Get specific row as a vector. Index is 0 based
109+-- | If the index is out of range then return empty vector
110+row :: ∀ a. Int -> Matrix a -> Vector a
111+row r (Dense nr nc vs) = A.slice i j vs
112+ where
113+ i = if r >=0 && r < nr then r*nc else 0
114+ j = if r >=0 && r < nr then i+nc else 0
115+
116+
117+-- | Get specific element. Index is 0 based
118+element :: ∀ a. Int -> Int -> Matrix a -> Maybe a
119+element r c (Dense nr nc vs) = A.index vs ((r*nc) + c)
120+
121+
122+-- | Return list of rows
123+rows :: ∀ a. Matrix a -> Array (Vector a)
124+rows mat = do
125+ i <- A.range 0 (nrows mat - 1)
126+ pure $ row i mat
127+
128+
129+-- | List of columns
130+columns :: ∀ a. Matrix a -> Array (Vector a)
131+columns mat = do
132+ i <- A.range 0 (ncols mat - 1)
133+ pure $ column i mat
134+
135+
136+-- | Slice matrix along the columns. Range is inclusive.
137+sliceCols :: ∀ a
138+ . Int -- ^ Starting column
139+ -> Int -- ^ End column
140+ -> Matrix a -- ^ Matrix to slice
141+ -> Matrix a -- ^ Slices matrix
142+sliceCols c1 c2 (Dense nr nc vs) = Dense nr (c2-c1+1) ds
143+ where
144+ ds = A.concatMap f (A.range 0 (nr-1))
145+ f :: Int -> Array a
146+ f r = A.slice (r*nc+c1) (r*nc+c2+1) vs
147+
148+
149+-- | Slice matrix along the rows. Range is inclusive.
150+sliceRows :: ∀ a
151+ . Int -- ^ Starting row
152+ -> Int -- ^ End row
153+ -> Matrix a -- ^ Matrix to slice
154+ -> Matrix a -- ^ Slices matrix
155+sliceRows r1 r2 (Dense nr nc vs) = Dense (r2-r1+1) nc ds
156+ where
157+ ds = A.slice (r1*nc) ((r2+1)*nc) vs
158+
159+
160+-- | Insert new column at a given index to the matrix
161+insertCol :: ∀ a. Int -> Vector a -> Matrix a -> Matrix a
162+insertCol i v (Dense nr nc vs) = Dense nr (nc+1) ds
163+ where
164+ ds = A.concatMap f (A.range 0 (nr-1))
165+ f :: Int -> Array a
166+ f r = A.slice (r*nc) (r*nc+i) vs <> A.slice r (r+1) v <> A.slice (r*nc+i) ((r+1)*nc) vs
167+
168+
169+-- | Multiply 2 matrices. The have to have compatible dimmensions
170+multiply :: Matrix Number -> Matrix Number -> Matrix Number
171+multiply xs ys
172+ | ncols xs /= nrows ys = zeros 1 1
173+ | otherwise = Dense (nrows xs) (ncols ys) $ multiply' (rows xs) (columns ys)
174+
175+multiply' :: Array (Vector Number) -> Array (Vector Number) -> Vector Number
176+multiply' rx cy = do
177+ r <- rx
178+ c <- cy
179+ pure $ fromMaybe 0.0 (V.dot r c)
180+
181+
182+-- | Transpose matrix
183+transpose :: ∀ a. Matrix a -> Matrix a
184+transpose (Dense r c ds) = Dense c r ds'
185+ where
186+ ds' = A.concat $ columns (Dense r c ds)
187+
188+
189+-- | Add 2 matrices. The should have the same size
190+add :: Matrix Number -> Matrix Number -> Matrix Number
191+add (Dense r1 c1 vs1) (Dense r2 c2 vs2)
192+ | r1 /= r2 || c1 /= c2 = zeros 1 1
193+ | otherwise = Dense r1 c1 $ V.add vs1 vs2
194+
src/LinearAlgebra/Vector.purs+65−1
@@ -1,8 +1,72 @@
11module LinearAlgebra.Vector
22 ( Vector
3+ , add
4+ , argmax
5+ , argmin
6+ , diff
7+ , dot
8+ , mulScalar
9+ , sum
310 ) where
411
12+import Prelude
13+import Global (infinity)
14+import Data.Array as A
15+import Data.Maybe (Maybe(..))
16+import Data.Tuple (Tuple(..), fst, snd)
17+
518
619-- | Dense Vector implementation
720type Vector = Array
8−
21+
22+
23+-- | Difference between 2 vectors.
24+-- | Vector have to have the same size
25+diff :: Vector Number -> Vector Number -> Vector Number
26+diff xs ys
27+ | (A.length xs) /= (A.length ys) = []
28+ | otherwise = A.zipWith (-) xs ys
29+
30+
31+-- | Add 2 vectors.
32+-- | Vector have to have the same size
33+add :: ∀ a. Semiring a => Vector a -> Vector a -> Vector a
34+add xs ys
35+ | (A.length xs) /= (A.length ys) = []
36+ | otherwise = A.zipWith (+) xs ys
37+
38+
39+-- | Dot product between 2 vectors
40+-- | https://en.wikipedia.org/wiki/Dot_product
41+dot :: ∀ a. Semiring a => Vector a -> Vector a -> Maybe a
42+dot xs ys
43+ | (A.length xs) /= (A.length ys) = Nothing
44+ | otherwise = Just $ sum (A.zipWith (*) xs ys)
45+
46+
47+-- | Sum vector elements
48+sum :: ∀ a. Semiring a => Vector a -> a
49+sum xs = A.foldl (+) zero xs
50+
51+
52+-- | Return index of the first maximum value
53+-- | This function will return 0 index for empty vector
54+argmax :: Vector Number -> Int
55+argmax vs = fst $ A.foldl f (Tuple 0 (-infinity)) xs
56+ where
57+ xs = A.mapWithIndex Tuple vs
58+ f tu tv = if (snd tv) > (snd tu) then tv else tu
59+
60+
61+-- | Return index of the first minimum value
62+-- | This function will return 0 index for empty vector
63+argmin :: Vector Number -> Int
64+argmin vs = fst $ A.foldl f (Tuple 0 infinity) xs
65+ where
66+ xs = A.mapWithIndex Tuple vs
67+ f tu tv = if (snd tv) < (snd tu) then tv else tu
68+
69+
70+-- | Multiply vector by scalar
71+mulScalar :: Number -> Vector Number -> Vector Number
72+mulScalar d vs = map (_ * d) vs
test/Main.purs+7−6
@@ -1,15 +1,16 @@
11module Test.Main where
22
33import Prelude
4−import Control.Monad.Eff (Eff)
5−import Control.Monad.Eff.Console (CONSOLE)
6−
7−import Test.Assert (ASSERT)
4+import Effect (Effect)
85
96import Test.LinearAlgebra.Matrix (testMatrix)
7+import Test.LinearAlgebra.Vector (testVector)
8+import Test.PerfTest (perfTests)
109
1110
12−main :: ∀ eff. Eff (console :: CONSOLE, assert :: ASSERT | eff) Unit
11+main :: Effect Unit
1312main = do
1413 testMatrix
15−
14+ testVector
15+ perfTests
16+
test/Test/LinearAlgebra/Matrix.purs+125−14
@@ -1,25 +1,136 @@
11module Test.LinearAlgebra.Matrix (testMatrix) where
22
33import Prelude
4−import Control.Monad.Eff (Eff)
5−import Control.Monad.Eff.Console (log, CONSOLE)
6−import Data.Maybe (fromJust, isJust, isNothing)
7−import Test.Assert (assert, ASSERT)
8−import Partial.Unsafe (unsafePartial)
4+import Effect (Effect)
5+import Effect.Class.Console (log)
6+import Data.Maybe (Maybe(..), fromMaybe, isJust, isNothing)
7+import Test.Assert (assert)
8+
99import LinearAlgebra.Matrix as M
1010
1111
12−testMatrix :: forall eff. Eff (console :: CONSOLE, assert :: ASSERT | eff) Unit
12+testMatrix :: Effect Unit
1313testMatrix = do
14+ log "\n# Test Matrix"
15+ testCreate
16+ testFromArray
17+ testGetRow
18+ testRows
19+ testGetValue
20+ testGetCol
21+ testColumns
22+ testSliceColumns
23+ testSliceRows
24+ testInsertColumn
25+ testMultiply
26+ testAdd
27+ testTranspose
28+ testIdentity
29+
1430
15− log "Init Matrix test case data"
16− let z1 = unsafePartial $ fromJust $ M.zeros 5 4
17− let z2 = unsafePartial $ fromJust $ M.fromArray 2 3 [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
31+testCreate :: Effect Unit
32+testCreate = do
33+ log " * Create matrix"
34+ let z1 = M.zeros 5 4
35+ assert $ M.nrows z1 == 5
36+ assert $ M.ncols z1 == 4
1837
19− log "Create matrix"
20− assert $ z1.nrows == 5
21− assert $ z1.ncols == 4
2238
23− log "From Array"
39+testFromArray :: Effect Unit
40+testFromArray = do
41+ log " * From Array"
2442 assert $ isNothing (M.fromArray 2 3 [1.0])
25− assert $ isJust (M.fromArray 2 3 [1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
43+ assert $ isJust (M.fromArray 2 3 [1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
44+
45+
46+testGetRow :: Effect Unit
47+testGetRow = do
48+ log " * Get specific row"
49+ let mat = fromMaybe (M.zeros 1 1) $ M.fromArray 2 3 [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
50+ assert $ M.row 0 mat == [1.0, 2.0, 3.0]
51+
52+
53+testGetCol :: Effect Unit
54+testGetCol = do
55+ log " * Get specific column"
56+ let mat = fromMaybe (M.zeros 1 1) $ M.fromArray 2 3 [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
57+ assert $ M.column 1 mat == [2.0, 5.0]
58+
59+
60+testGetValue :: Effect Unit
61+testGetValue = do
62+ log " * Get specific value"
63+ let mat = fromMaybe (M.zeros 1 1) $ M.fromArray 2 3 [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
64+ assert $ M.element 1 2 mat == Just 6.0
65+
66+
67+testRows :: Effect Unit
68+testRows = do
69+ log " * Get list of rows"
70+ let mat = fromMaybe (M.zeros 1 1) $ M.fromArray 2 3 [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
71+ assert $ M.rows mat == [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]
72+
73+
74+testColumns :: Effect Unit
75+testColumns = do
76+ log " * Get list of columns"
77+ let mat = fromMaybe (M.zeros 1 1) $ M.fromArray 2 3 [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
78+ assert $ M.columns mat == [[1.0, 4.0], [2.0, 5.0], [3.0, 6.0]]
79+
80+
81+testSliceColumns :: Effect Unit
82+testSliceColumns = do
83+ log " * Slice columns"
84+ let mat = fromMaybe (M.zeros 1 1) $ M.fromArray 2 3 [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
85+ let expected = fromMaybe (M.zeros 1 1) $ M.fromArray 2 2 [1.0, 2.0, 4.0, 5.0]
86+ assert $ M.sliceCols 0 1 mat == expected
87+
88+
89+testSliceRows :: Effect Unit
90+testSliceRows = do
91+ log " * Slice rows"
92+ let mat = fromMaybe (M.zeros 1 1) $ M.fromArray 3 2 [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
93+ let expected = fromMaybe (M.zeros 1 1) $ M.fromArray 2 2 [1.0, 2.0, 3.0, 4.0]
94+ assert $ M.sliceRows 0 1 mat == expected
95+
96+
97+testInsertColumn :: Effect Unit
98+testInsertColumn = do
99+ log " * Insert column"
100+ let mat = fromMaybe (M.zeros 1 1) $ M.fromArray 2 2 [1.0, 2.0, 4.0, 5.0]
101+ let expected1 = fromMaybe (M.zeros 1 1) $ M.fromArray 2 3 [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
102+ let expected2 = fromMaybe (M.zeros 1 1) $ M.fromArray 2 3 [3.0, 1.0, 2.0, 6.0, 4.0, 5.0]
103+ assert $ M.insertCol 2 [3.0, 6.0] mat == expected1
104+ assert $ M.insertCol 0 [3.0, 6.0] mat == expected2
105+
106+
107+testAdd :: Effect Unit
108+testAdd = do
109+ log " * Multiply matrices"
110+ let m1 = fromMaybe (M.zeros 1 1) $ M.fromArray 2 3 [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
111+ let expected = fromMaybe (M.zeros 1 1) $ M.fromArray 2 3 [2.0, 4.0, 6.0, 8.0, 10.0, 12.0]
112+ assert $ M.add m1 m1 == expected
113+
114+
115+testMultiply :: Effect Unit
116+testMultiply = do
117+ log " * Multiply matrices"
118+ let m1 = fromMaybe (M.zeros 1 1) $ M.fromArray 2 3 [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
119+ let m2 = fromMaybe (M.zeros 1 1) $ M.fromArray 3 2 [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
120+ let expected = fromMaybe (M.zeros 1 1) $ M.fromArray 2 2 [22.0, 28.0, 49.0, 64.0]
121+ assert $ M.multiply m1 m2 == expected
122+
123+
124+testTranspose :: Effect Unit
125+testTranspose = do
126+ log " * Transpose matrix"
127+ let mat = fromMaybe (M.zeros 1 1) $ M.fromArray 2 3 [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
128+ let expected = fromMaybe (M.zeros 1 1) $ M.fromArray 3 2 [1.0, 4.0, 2.0, 5.0, 3.0, 6.0]
129+ assert $ M.transpose mat == expected
130+
131+
132+testIdentity :: Effect Unit
133+testIdentity = do
134+ log " * Identity matrix"
135+ let expected = fromMaybe (M.zeros 1 1) $ M.fromArray 3 3 [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]
136+ assert $ M.identity 3 == expected
test/Test/LinearAlgebra/Vector.pursnew file+59
@@ -0,0 +1,59 @@
1+module Test.LinearAlgebra.Vector (testVector) where
2+
3+import Prelude
4+import Effect (Effect)
5+import Effect.Class.Console (log)
6+import Data.Maybe (Maybe(..))
7+import Test.Assert (assert)
8+
9+import LinearAlgebra.Vector as V
10+
11+
12+testVector :: Effect Unit
13+testVector = do
14+ log "\n# Vector tests"
15+ testAdd
16+ testDifference
17+ testDot
18+ testMulScalar
19+ testArgMax
20+ testtestArgMin
21+
22+
23+testAdd :: Effect Unit
24+testAdd = do
25+ log " * Add vectors"
26+ assert $ V.add [1.0, 2.0, -3.0] [1.0, 1.0, 1.0, 5.0] == []
27+ assert $ V.add [1.0, 2.0, -3.0] [1.0, 1.0, 1.0] == [2.0, 3.0, -2.0]
28+
29+
30+testDifference :: Effect Unit
31+testDifference = do
32+ log " * Difference"
33+ assert $ V.diff [1.0, 2.0, -3.0] [1.0, 1.0, 1.0, 5.0] == []
34+ assert $ V.diff [1.0, 2.0, -3.0] [1.0, 1.0, 1.0] == [0.0, 1.0, -4.0]
35+
36+
37+testDot :: Effect Unit
38+testDot = do
39+ log " * Dot product"
40+ assert $ V.dot [1.0, 2.0, -3.0] [1.0, 2.0, 1.0] == Just 2.0
41+
42+
43+testMulScalar :: Effect Unit
44+testMulScalar = do
45+ log " * Multiply by scalar"
46+ assert $ V.mulScalar 2.0 [1.0, 2.0, -3.0] == [2.0, 4.0, -6.0]
47+
48+
49+testArgMax :: Effect Unit
50+testArgMax = do
51+ log " * argmax"
52+ assert $ V.argmax [1.0, 2.0, -3.0, 1.0, 2.0, 1.0] == 1
53+
54+
55+testtestArgMin :: Effect Unit
56+testtestArgMin = do
57+ log " * argmin"
58+ assert $ V.argmin [1.0, 2.0, -3.0, 1.0, 2.0, 1.0] == 2
59+
test/Test/PerfTest.pursnew file+28
@@ -0,0 +1,28 @@
1+module Test.PerfTest (perfTests) where
2+
3+import Prelude
4+import Effect (Effect)
5+import Effect.Class.Console (log)
6+import Data.Array as A
7+import Data.Maybe (fromJust)
8+import Test.Assert (assert)
9+import Partial.Unsafe (unsafePartial)
10+import LinearAlgebra.Matrix as M
11+
12+
13+perfTests :: Effect Unit
14+perfTests = do
15+
16+ log "\n# Run Benchmarks"
17+
18+ log "\nGet columns"
19+ let a1 = unsafePartial $ fromJust $ M.replicate 1000000 1 1.0
20+ let cs = M.columns a1
21+ log $ "There are " <> show (A.length cs) <> " columns."
22+
23+ log "\nGet rows"
24+ let a2 = unsafePartial $ fromJust $ M.replicate 10 1000000 1.0
25+ let rs = M.rows a2
26+ log $ "There are " <> show (A.length rs) <> " rows."
27+
28+ assert true