git.iko.soy/rowtype-yoga/purescript-linear-algebra
on master help sign in
9 files changed+307−69
since splitsnapshots
unifiedsplit
LICENSE+1−1 README.md+9−1 bower.json+7−7 src/LinearAlgebra/Matrix.purs+111−5 src/LinearAlgebra/Vector.purs+13−7 test/Main.purs+3−6 test/Test/LinearAlgebra/Matrix.purs+117−22 test/Test/LinearAlgebra/Vector.purs+42−16 test/Test/PerfTest.purs+4−4
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+9−1
@@ -21,8 +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
29+ * Vector
30+ * [x] Dot product
31+ * [x] Multiply by scalar
32+ * [x] Add, differentiate
33+ * [x] ArgMax, ArgMin
2634
2735
2836## Documentation
bower.json+7−7
@@ -14,14 +14,14 @@
1414 "output"
1515 ],
1616 "dependencies": {
17− "purescript-prelude": "^3.0.0",
18− "purescript-arrays": "^4.0.1",
19− "purescript-globals": "^3.0.0",
20− "purescript-tuples": "^4.0.0"
17+ "purescript-prelude": "^4.1.0",
18+ "purescript-arrays": "^5.0.0",
19+ "purescript-globals": "^4.0.0",
20+ "purescript-tuples": "^5.0.0"
2121 },
2222 "devDependencies": {
23− "purescript-assert": "^3.0.0",
24− "purescript-console": "^3.0.0",
25− "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"
2626 }
2727}
src/LinearAlgebra/Matrix.purs+111−5
@@ -1,9 +1,12 @@
11module LinearAlgebra.Matrix
2− ( Matrix
2+ ( Matrix
33 -- * Constructors
44 , fromArray
5+ , fromColumn
6+ , fromRow
57 , replicate
68 , zeros
9+ , identity
710 -- * Access data
811 , ncols
912 , nrows
@@ -12,18 +15,36 @@ module LinearAlgebra.Matrix
1215 , row
1316 , columns
1417 , rows
15− -- * Mapping over matrix
18+ , sliceCols
19+ , sliceRows
20+ , toVector
21+ -- * Modify data
22+ , insertCol
23+ , transpose
24+ -- * Matrix operations
25+ , add
26+ , multiply
1627 ) where
1728
1829import Prelude
1930import Data.Array as A
20−import Data.Maybe (Maybe(..))
31+import Data.Maybe (Maybe(..), fromMaybe)
2132import LinearAlgebra.Vector (Vector)
33+import LinearAlgebra.Vector as V
2234
2335
2436-- | Dense Matrix implementation
2537data Matrix a = Dense Int Int (Array a) -- nrow, ncol, values
2638
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+
2748-- | Number of rows in matrix
2849nrows :: ∀ a. Matrix a -> Int
2950nrows (Dense r _ _) = r
@@ -32,6 +53,10 @@ nrows (Dense r _ _) = r
3253ncols :: ∀ a. Matrix a -> Int
3354ncols (Dense _ c _) = c
3455
56+-- | Convert Matrix to Vector
57+toVector :: ∀ a. Matrix a -> Vector a
58+toVector (Dense _ _ vs) = vs
59+
3560
3661-- | Create array of given dimmension containing replicated value
3762replicate :: ∀ a. Int -> Int -> a -> Maybe (Matrix a )
@@ -40,7 +65,6 @@ replicate r c v | r > 0 && c > 0 = Just $ Dense r c (A.replicate (r * c) v)
4065
4166
4267-- | Create array of given dimmension with all values set to 0
43−-- |
4468zeros :: Int -> Int -> Matrix Number
4569zeros r c = Dense r' c' (A.replicate (r' * c') 0.0)
4670 where
@@ -48,12 +72,33 @@ zeros r c = Dense r' c' (A.replicate (r' * c') 0.0)
4872 c' = if c > 0 then c else 1
4973
5074
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
84+
85+
5186-- | Create Matrix from Array
5287fromArray :: ∀ a. Int -> Int -> Array a -> Maybe (Matrix a)
5388fromArray r c vs | r > 0 && c > 0 && r*c == A.length vs = Just (Dense r c vs)
5489 | otherwise = Nothing
5590
5691
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+
57102-- | Get specific column as a vector. Index is 0 based
58103-- | If the index is out of range then return empty vector
59104column :: ∀ a. Int -> Matrix a -> Vector a
@@ -85,4 +130,65 @@ rows mat = do
85130columns :: ∀ a. Matrix a -> Array (Vector a)
86131columns mat = do
87132 i <- A.range 0 (ncols mat - 1)
88− pure $ column i mat
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+13−7
@@ -5,6 +5,7 @@ module LinearAlgebra.Vector
55 , argmin
66 , diff
77 , dot
8+ , mulScalar
89 , sum
910 ) where
1011
@@ -21,18 +22,18 @@ type Vector = Array
2122
2223-- | Difference between 2 vectors.
2324-- | Vector have to have the same size
24−diff :: Vector Number -> Vector Number -> Maybe (Vector Number)
25+diff :: Vector Number -> Vector Number -> Vector Number
2526diff xs ys
26− | (A.length xs) /= (A.length ys) = Nothing
27− | otherwise = Just $ A.zipWith (-) xs ys
27+ | (A.length xs) /= (A.length ys) = []
28+ | otherwise = A.zipWith (-) xs ys
2829
2930
3031-- | Add 2 vectors.
3132-- | Vector have to have the same size
32−add :: ∀ a. Semiring a => Vector a -> Vector a -> Maybe (Vector a)
33+add :: ∀ a. Semiring a => Vector a -> Vector a -> Vector a
3334add xs ys
34− | (A.length xs) /= (A.length ys) = Nothing
35− | otherwise = Just $ A.zipWith (+) xs ys
35+ | (A.length xs) /= (A.length ys) = []
36+ | otherwise = A.zipWith (+) xs ys
3637
3738
3839-- | Dot product between 2 vectors
@@ -63,4 +64,9 @@ argmin :: Vector Number -> Int
6364argmin vs = fst $ A.foldl f (Tuple 0 infinity) xs
6465 where
6566 xs = A.mapWithIndex Tuple vs
66− f tu tv = if (snd tv) < (snd tu) then tv else tu
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+3−6
@@ -1,19 +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)
107import Test.LinearAlgebra.Vector (testVector)
118import Test.PerfTest (perfTests)
129
1310
14−main :: ∀ eff. Eff (console :: CONSOLE, assert :: ASSERT | eff) Unit
11+main :: Effect Unit
1512main = do
1613 testMatrix
1714 testVector
1815 perfTests
19−
16+
test/Test/LinearAlgebra/Matrix.purs+117−22
@@ -1,41 +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 (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
1429
15− log "Init Matrix test case data"
16− let z1 = 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]
1830
19− log "Create matrix"
31+testCreate :: Effect Unit
32+testCreate = do
33+ log " * Create matrix"
34+ let z1 = M.zeros 5 4
2035 assert $ M.nrows z1 == 5
2136 assert $ M.ncols z1 == 4
2237
23− log "From Array"
38+
39+testFromArray :: Effect Unit
40+testFromArray = do
41+ log " * From Array"
2442 assert $ isNothing (M.fromArray 2 3 [1.0])
2543 assert $ isJust (M.fromArray 2 3 [1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
2644
27− log "Get specific row"
28− assert $ M.row 0 z2 == [1.0, 2.0, 3.0]
2945
30− log "Get specific column"
31− assert $ M.column 1 z2 == [2.0, 5.0]
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+
32123
33− log "Get specific value"
34− assert $ M.element 1 2 z2 == Just 6.0
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
35130
36− log "Get list of rows"
37− assert $ M.rows z2 == [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]
38131
39− log "Get list of columns"
40− assert $ M.columns z2 == [[1.0, 4.0], [2.0, 5.0], [3.0, 6.0]]
41−
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.purs+42−16
@@ -1,33 +1,59 @@
11module Test.LinearAlgebra.Vector (testVector) where
22
33import Prelude
4−import Control.Monad.Eff (Eff)
5−import Control.Monad.Eff.Console (log, CONSOLE)
4+import Effect (Effect)
5+import Effect.Class.Console (log)
66import Data.Maybe (Maybe(..))
7−import Test.Assert (assert, ASSERT)
7+import Test.Assert (assert)
88
99import LinearAlgebra.Vector as V
1010
1111
12−testVector :: forall eff. Eff (console :: CONSOLE, assert :: ASSERT | eff) Unit
12+testVector :: Effect Unit
1313testVector = do
14+ log "\n# Vector tests"
15+ testAdd
16+ testDifference
17+ testDot
18+ testMulScalar
19+ testArgMax
20+ testtestArgMin
1421
15− log "\n#Vector tests"
1622
17− log "Add vectors"
18− assert $ V.add [1.0, 2.0, -3.0] [1.0, 1.0, 1.0, 5.0] == Nothing
19− assert $ V.add [1.0, 2.0, -3.0] [1.0, 1.0, 1.0] == Just [2.0, 3.0, -2.0]
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]
2028
21− log "Difference"
22− assert $ V.diff [1.0, 2.0, -3.0] [1.0, 1.0, 1.0, 5.0] == Nothing
23− assert $ V.diff [1.0, 2.0, -3.0] [1.0, 1.0, 1.0] == Just [0.0, 1.0, -4.0]
2429
25− log "Dot product"
26− assert $ V.dot [1.0, 2.0, -3.0] [1.0, 2.0, 1.0] == Just (2.0)
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]
2735
28− log "argmax"
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"
2952 assert $ V.argmax [1.0, 2.0, -3.0, 1.0, 2.0, 1.0] == 1
3053
31− log "argmin"
54+
55+testtestArgMin :: Effect Unit
56+testtestArgMin = do
57+ log " * argmin"
3258 assert $ V.argmin [1.0, 2.0, -3.0, 1.0, 2.0, 1.0] == 2
33−
59+
test/Test/PerfTest.purs+4−4
@@ -1,16 +1,16 @@
11module Test.PerfTest (perfTests) where
22
33import Prelude
4−import Control.Monad.Eff (Eff)
5−import Control.Monad.Eff.Console (log, CONSOLE)
4+import Effect (Effect)
5+import Effect.Class.Console (log)
66import Data.Array as A
77import Data.Maybe (fromJust)
8−import Test.Assert (assert, ASSERT)
8+import Test.Assert (assert)
99import Partial.Unsafe (unsafePartial)
1010import LinearAlgebra.Matrix as M
1111
1212
13−perfTests :: forall eff. Eff (console :: CONSOLE, assert :: ASSERT | eff) Unit
13+perfTests :: Effect Unit
1414perfTests = do
1515
1616 log "\n# Run Benchmarks"