Skip to content

Commit df9b51c

Browse files
authored
Adding a few convenience functions and tests (bmsherman#13)
* exporting MType * adding untyped abstract value usage * added demonstration of abstract type usage to tests * added serialization functions and tests; added (untested) clearVar * started adding some cell array tests * added some convenience functions for extracting data from cell arrays, and an associated engine test * Revert "exporting MType" This reverts commit 6931edb.
1 parent 4c57a51 commit df9b51c

File tree

10 files changed

+194
-19
lines changed

10 files changed

+194
-19
lines changed

Diff for: .gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
.stack-work
22
patchMatlab.lock
3+
4+
# Editor files
5+
*~

Diff for: Foreign/Matlab/Array.hsc

+17
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ module Foreign.Matlab.Array (
4141
mStructGet, mStructSet,
4242
mStructSetFields,
4343
mStructAddField, mStructRemoveField,
44+
mxCellGetAllOfType, mxCellGetArraysOfType,
4445

4546
-- ** Object access
4647
-- |Some structs are also validated (blessed) user objects.
@@ -52,6 +53,7 @@ import Foreign
5253
import Foreign.C.String
5354
import Foreign.C.Types
5455
import Data.Complex
56+
import Data.Maybe (catMaybes)
5557
import Foreign.Matlab.Util
5658
import Foreign.Matlab.Internal
5759
import Foreign.Matlab.Types
@@ -103,6 +105,7 @@ mxArraySetSize a s = do
103105
when (r /= 0) $ fail "mxArraySetSize"
104106

105107
foreign import ccall unsafe mxGetNumberOfElements :: MXArrayPtr -> IO CSize
108+
-- |Like `numel` in MATLAB.
106109
mxArrayLength :: MXArray a -> MIO Int
107110
mxArrayLength a = ii =.< withMXArray a mxGetNumberOfElements
108111

@@ -212,6 +215,20 @@ castMXArray a
212215
b :: MXArray a
213216
b = unsafeCastMXArray a
214217

218+
-- | Extract all arrays of a given type from a Cell Array.
219+
mxCellGetArraysOfType :: MXArrayComponent a => MXArray MCell -> MIO ([MXArray a])
220+
mxCellGetArraysOfType ca = do
221+
cellVals <- (fmap . fmap) mCell (mxArrayGetAll ca)
222+
mxaMays :: [Maybe (MXArray a)] <- sequence $ castMXArray <$> cellVals
223+
pure $ catMaybes mxaMays
224+
225+
-- | A convenience function to extract all arrays of a given type from a Cell Array;
226+
-- | may have larger dimensions than the original Cell Array due to flattening.
227+
mxCellGetAllOfType :: MXArrayComponent a => MXArray MCell -> MIO [a]
228+
mxCellGetAllOfType ca = do
229+
as <- mxCellGetArraysOfType ca
230+
join <$> (sequence $ mxArrayGetAll <$> as)
231+
215232
foreign import ccall unsafe mxGetData :: MXArrayPtr -> IO (Ptr a)
216233

217234
class (MXArrayComponent a, MType mx a, Storable mx) => MXArrayData mx a where

Diff for: Foreign/Matlab/Engine.hsc

+4-4
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ data EngineEvalArg a = EvalArray (MXArray a) | EvalVar String | EvalStr String
7171
-- |Evaluate a function with the given arguments and number of results.
7272
-- This automates 'engineSetVar' on arguments (using \"hseval_inN\"), 'engineEval', and 'engineGetVar' on results (using \"hseval_outN\").
7373
engineEvalFun :: Engine -> String -> [EngineEvalArg a] -> Int -> IO [MAnyArray]
74-
engineEvalFun eng fun arg no = do
75-
arg <- zipWithM makearg arg [1 :: Int ..]
74+
engineEvalFun eng fun args no = do
75+
arg <- zipWithM makearg args [1 :: Int ..]
7676
let out = map makeout [1..no]
7777
let outs = if out == [] then "" else "[" ++ unwords out ++ "] = "
7878
engineEval eng (outs ++ fun ++ "(" ++ intercalate "," arg ++ ")")
@@ -88,8 +88,8 @@ engineEvalFun eng fun arg no = do
8888

8989
-- |Convenience function for calling functions that do not return values (i.e. "procedures").
9090
engineEvalProc :: Engine -> String -> [EngineEvalArg a] -> IO ()
91-
engineEvalProc eng fun arg = do
92-
_ <- engineEvalFun eng fun arg 0
91+
engineEvalProc eng fun args = do
92+
_ <- engineEvalFun eng fun args 0
9393
pure ()
9494

9595
-- |Utility function to quote a string

Diff for: Foreign/Matlab/Engine/Wrappers.hs

+44-6
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,57 @@
11
{-|
2-
Wrappers to common MATLAB functions using the Matlab engine.
2+
Wrappers to common (and some uncommon) MATLAB functions using the MATLAB Engine.
33
44
Note that you cannot use "Foreign.Matlab.Engine" and "Foreign.Matlab.Runtime" in the same program.
55
This seems to be a Matlab limitation.
66
-}
77

88
module Foreign.Matlab.Engine.Wrappers (
9-
addpath
10-
) where
9+
addpath
10+
, clearVar
11+
, getArrayFromByteStream
12+
, getByteStreamFromArray
13+
, MEither(..), isMLeft, isMRight
14+
) where
1115

16+
import Foreign.Matlab
1217
import Foreign.Matlab.Engine
1318
import Path
1419

1520
-- | We require an absolute path in this case
1621
addpath :: Engine -> Path Abs Dir -> IO ()
17-
addpath eng p = do
18-
engineEvalProc eng "addpath" [EvalStr $ toFilePath p]
19-
-- engineEval eng $ "addpath(" <> toFilePath p <> "');"
22+
addpath eng p = engineEvalProc eng "addpath" [EvalStr $ toFilePath p]
23+
24+
-- | TODO: add a test for this, likely not working as expected.
25+
-- | Clears a variable from the engine's workspace
26+
clearVar :: Engine -> String -> IO ()
27+
clearVar eng var = engineEvalProc eng "clear" [EvalStr var]
28+
29+
-- | Wraps an undocumented function to serialize a MATLAB object.
30+
getByteStreamFromArray :: Engine -> MAnyArray -> IO (Either String [MUint8])
31+
getByteStreamFromArray eng mObj = do
32+
[byteStream] <- engineEvalFun eng "getByteStreamFromArray" [EvalArray mObj] 1
33+
mxArrMay <- castMXArray byteStream
34+
case mxArrMay of
35+
Just mxArr -> Right <$> mxArrayGetAll mxArr
36+
Nothing -> pure $ Left
37+
"getByteStreamFromArray: Couldn't convert MATLAB bytestream to [MUint8]"
38+
39+
-- | Wraps an undocumented function to deserialize a MATLAB object.
40+
getArrayFromByteStream :: Engine -> [MUint8] -> IO MAnyArray
41+
getArrayFromByteStream eng bytes = do
42+
matBsArr <- createMXArray [length bytes]
43+
mxArraySetAll matBsArr bytes
44+
[mObj] <- engineEvalFun eng "getArrayFromByteStream" [EvalArray matBsArr] 1
45+
pure mObj
46+
47+
newtype MEither = MEither {unMXEither :: MStructArray}
48+
49+
isMLeft :: MEither -> IO Bool
50+
isMLeft me = do
51+
sFields <- mStructFields $ unMXEither me
52+
pure $ "left" `elem` sFields
53+
54+
isMRight :: MEither -> IO Bool
55+
isMRight me = do
56+
sFields <- mStructFields $ unMXEither me
57+
pure $ "right" `elem` sFields

Diff for: matlab.cabal

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ executable matlab-engine-test
103103
Test.UtilTemplate
104104
hs-source-dirs:
105105
test
106-
ghc-options: -threaded -rtsopts -with-rtsopts=-N
106+
ghc-options: -threaded -rtsopts -with-rtsopts=-N -fno-ignore-asserts
107107
build-depends:
108108
base >= 4 && <5
109109
, exceptions

Diff for: test/Test/Engine.hs

+113-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
{-# LANGUAGE QuasiQuotes #-}
2+
{-# LANGUAGE ScopedTypeVariables #-}
23
{-# LANGUAGE TemplateHaskell #-}
34

45
module Test.Engine where
56

7+
import Control.Exception (SomeException, assert, try)
8+
import Data.Either (isLeft, isRight, lefts)
69
import Foreign.Matlab
710
import Foreign.Matlab.Engine
8-
import Foreign.Matlab.Engine.Wrappers (addpath)
11+
import Foreign.Matlab.Engine.Wrappers
912
import Language.Haskell.TH (Q, runIO)
1013
import Language.Haskell.TH.Syntax (lift)
1114
import Path
@@ -15,11 +18,19 @@ engineTests = runEngineTests ""
1518

1619
runEngineTests :: String -> IO ()
1720
runEngineTests host = do
18-
putStrLn " -- Starting engine --"
21+
putStrLn "-- Starting engine --"
1922
eng <- newEngine host
20-
putStrLn " -- Engine created --"
23+
putStrLn "-- Engine created --"
24+
let testPath = repoDir </> testRel
25+
addpath eng testPath
2126
runLocalMatFun eng
2227
cosOfPi eng
28+
testAbstractValueUse eng
29+
testTypedAbstractValueUse eng
30+
testGetByteStreamFromArray eng
31+
testGetArrayFromByteStream eng
32+
testCellGet eng
33+
testClearVar eng
2334

2435
cosOfPi :: Engine -> IO ()
2536
cosOfPi eng = do
@@ -30,12 +41,7 @@ cosOfPi eng = do
3041
runLocalMatFun :: Engine -> IO ()
3142
runLocalMatFun eng = do
3243
putStrLn "\n-- mtest: cos pi --"
33-
let testPath = repoDir </> testRel
3444
x <- createMXScalar (pi :: MDouble)
35-
addpath eng testPath
36-
-- let addTestPath = "addpath " <> (toFilePath testPath)
37-
-- putStrLn $ "evaluating: " <> addTestPath
38-
-- engineEval eng addTestPath
3945
cosBody eng "mtest" x
4046

4147
cosBody :: Engine -> String -> MXArray MDouble -> IO ()
@@ -46,6 +52,105 @@ cosBody eng cosFun x = do
4652
y <- mxScalarGet y
4753
print (y :: MDouble)
4854

55+
testAbstractValueUse :: Engine -> IO ()
56+
testAbstractValueUse eng = do
57+
putStrLn $ "\n-- testAbstractValueUse --"
58+
sOut <- makeTestStruct eng
59+
sSum <- useTestStruct eng sOut
60+
let sSumRes = assert (sSum == 7.0) sSum
61+
putStrLn $ " struct sum is: " <> (show sSumRes)
62+
63+
makeTestStruct :: Engine -> IO MAnyArray
64+
makeTestStruct eng = do
65+
[res] <- engineEvalFun eng "makeTestStruct" [] 1
66+
pure res
67+
68+
useTestStruct :: Engine -> MAnyArray -> IO MDouble
69+
useTestStruct eng sIn = do
70+
[res] <- engineEvalFun eng "useTestStruct" [EvalArray sIn] 1
71+
mxArrMay <- castMXArray res
72+
case mxArrMay of
73+
Just mxArr -> mxScalarGet mxArr
74+
Nothing -> pure 0.0
75+
76+
77+
newtype MyAbsType = MyAbsType { unMyAbsType :: MAnyArray }
78+
79+
-- |Similar to testAbstractValueUse, but instead of using
80+
-- |MAnyArray, we use newtypes for better type safety
81+
testTypedAbstractValueUse :: Engine -> IO ()
82+
testTypedAbstractValueUse eng = do
83+
putStrLn $ "\n-- testTypedAbstractValueUse --"
84+
sOut <- makeTestStructTyped eng
85+
sSum <- useTestStructTyped eng sOut
86+
let sSumRes = assert (sSum == 7.0) sSum
87+
putStrLn $ " struct sum is: " <> (show sSumRes)
88+
89+
makeTestStructTyped :: Engine -> IO MyAbsType
90+
makeTestStructTyped eng = MyAbsType <$> (makeTestStruct eng)
91+
92+
useTestStructTyped :: Engine -> MyAbsType -> IO MDouble
93+
useTestStructTyped eng (MyAbsType sIn) = useTestStruct eng sIn
94+
95+
testGetByteStreamFromArray :: Engine -> IO ()
96+
testGetByteStreamFromArray eng = do
97+
putStrLn $ "\n-- testGetByteStreamFromArray --"
98+
sOutBSMatlab <- makeTestStructByteStream eng
99+
sOut <- makeTestStruct eng
100+
Right sOutBSHaskell <- getByteStreamFromArray eng sOut
101+
let bsSum = sum $ fromIntegral <$> (assert (sOutBSMatlab == sOutBSHaskell) sOutBSHaskell)
102+
putStrLn $ " bytestream sum is: " <> (show bsSum)
103+
104+
testGetArrayFromByteStream :: Engine -> IO ()
105+
testGetArrayFromByteStream eng = do
106+
putStrLn $ "\n-- testGetArrayFromByteStream --"
107+
sOutBS <- makeTestStructByteStream eng
108+
sOutFromBS <- getArrayFromByteStream eng sOutBS
109+
sOut <- makeTestStruct eng
110+
sSumFromBS <- useTestStruct eng sOutFromBS
111+
sSum <- useTestStruct eng sOut
112+
let sSumRes = assert (sSumFromBS == sSum) sSumFromBS
113+
putStrLn $ " deserialized struct sum is: " <> (show sSumRes)
114+
115+
makeTestStructByteStream :: Engine -> IO [MUint8]
116+
makeTestStructByteStream eng = do
117+
[res] <- engineEvalFun eng "makeTestStructByteStream" [] 1
118+
mxArrMay <- castMXArray res
119+
case mxArrMay of
120+
Just mxArr -> mxArrayGetAll mxArr
121+
Nothing -> pure []
122+
123+
-- TODO: display cell array and extracted values in test
124+
testCellGet :: Engine -> IO ()
125+
testCellGet eng = do
126+
putStrLn "\n-- testCellGet --"
127+
[ca] <- engineEvalFun eng "mcellTest" [] 1
128+
Just (ca :: MXArray MCell) <- castMXArray ca
129+
caLen <- mxArrayLength ca
130+
let caLenMsg = assert (caLen == 6) "cell array has length 6"
131+
putStrLn caLenMsg
132+
dCells :: [MXArray MDouble] <- mxCellGetArraysOfType ca
133+
let dCellsMsg = assert (length dCells == 4) "cell array has 4 double arrays"
134+
putStrLn dCellsMsg
135+
dVals :: [MDouble] <- mxCellGetAllOfType ca
136+
let dValsMsg = assert (length dVals == 4) "cell array has 4 double values"
137+
putStrLn dValsMsg
138+
139+
testClearVar :: Engine -> IO ()
140+
testClearVar eng = do
141+
putStrLn $ "\n-- testClearVar --"
142+
let foopi = "foopi"
143+
x <- createMXScalar (pi :: MDouble)
144+
engineSetVar eng foopi x
145+
ei1 :: Either SomeException MAnyArray <- try $ engineGetVar eng foopi
146+
putStrLn $ assert (isRight ei1) " Can clearVar once"
147+
clearVar eng foopi
148+
ei2 :: Either SomeException MAnyArray <- try $ engineGetVar eng foopi
149+
putStrLn $ assert (isLeft ei2) $
150+
" Can't clearVar twice: " <> (show $ lefts [ei2])
151+
putStrLn " Finished testClearVar"
152+
153+
49154
testRel :: Path Rel Dir
50155
testRel = $(mkRelDir "test")
51156

Diff for: test/makeTestStruct.m

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
function sOut = makeTestStruct()
2+
sOut = struct('x', 2, 'y', 5);
3+
end

Diff for: test/makeTestStructByteStream.m

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
function sOutBS = makeTestStructByteStream()
2+
sOut = makeTestStruct();
3+
sOutBS = getByteStreamFromArray(sOut);
4+
end

Diff for: test/mcellTest.m

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
function y = mcellTest(x)
2+
y = {'ads', '123', 4; 7 8 9};

Diff for: test/useTestStruct.m

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
function sumOut = useTestStruct(sIn)
2+
sumOut = sIn.x + sIn.y;
3+
end

0 commit comments

Comments
 (0)