samer@0: *** Prolog Matlab interface samer@0: *** samer@0: *** Authors: samer@0: *** Samer Abdallah samer@0: *** Centre for Digital Music, samer@0: *** Queen Mary, University of London samer@0: *** samer@0: *** Christophe Rhodes samer@0: *** Centre for Computational Creativity samer@0: *** Goldsmiths College, University of London samer@0: *** samer@19: *** 2004--2012 samer@0: samer@0: samer@8: ------------------------------------------------------------------------- samer@8: NB: output type tagging system has changed but this documentation samer@8: has not yet. See pldoc documentation of plml.pl. samer@8: samer@19: Also: you need to make sure the required Matlab dynamic libraries are samer@19: on your dyld search path before starting SWI Prolog. The bash script samer@19: swiplml shows how you can do this by setting the DYLD_FALLBACK_LIBRARY_PATH samer@19: environment variable before calling swipl, but be aware that there samer@19: are several environment variables that can affect dynamic library loading samer@19: and that they can have weird effects on your system if you have conflicting samer@19: libraries is different places. See the dyld man page ('man dyld'). samer@0: samer@0: samer@0: ------------------------------------------------------------------------- samer@0: OVERVIEW samer@0: samer@0: PLML is a foreign interface that enables Matlab to be used as a computational samer@0: engine from within SWI Prolog. The basic idea is that instead of using samer@0: the standard is/2 operator to evaluate a certain class of terms, we can samer@0: use the ===/2 operator to get Matlab to evaluate a (much richer) class of samer@0: terms, eg samer@0: samer@0: ?- float(A)===trace(eye(3)). samer@0: samer@0: A = 3.0 samer@0: samer@0: We can also get Matlab to perform actions with side effects, like samer@0: making sounds and graphics; obviously these do not fit into the declartive samer@0: semantics of Prolog and have to be dealt with under the procedural semantics. samer@0: If you want to execute a Matlab command in an imperative way and see the samer@0: textual output, use the ??/1 operator, eg samer@0: samer@0: ?- ??disp(`hello). samer@0: >> hello samer@0: samer@0: samer@0: samer@0: The interface works by using the Matlab Engine API, which starts up a Matlab samer@0: process on the end of a pipe. The Matlab process can be on another machine, samer@0: and multiple Matlab engines can be started on the same or different machines. samer@0: Matlab expressions are sent down the pipe and executed. Matlab's textual samer@0: output comes back through the pipe. In addition, Matlab variables can be samer@0: transferred directly between the Matlab engine's memory space and SWI's samer@0: memory space. samer@0: samer@0: samer@0: *** Expression language samer@0: samer@0: Expressions to evaluate are given in a sublanguage of terms which is samer@0: similar to but not exactly the same as Matlab. In particular, Prolog samer@0: syntax cannot accommodate the single quoted Matlab strings, samer@0: the Matlab syntax of matrices, (eg [1 2; 3 4]), and the Matlab syntax samer@0: for slicing arrays (eg A(:,3:4)) if A is a Prolog variable. samer@0: Strings are handled using the q/1 or `/1 functors, ie `hello and q(hello) samer@0: both evaluate to 'hello'. Arrays can be given either as flat lists, samer@0: which are interpreted as horizontal concatenation as in Matlab: samer@0: samer@0: ?- ??[1,2,3]. samer@0: >> ans = 1 2 3 samer@0: samer@0: ?- ??[eye(2),magic(2)]. samer@0: >> ans = samer@0: 1 0 1 3 samer@0: 0 1 4 2 samer@0: samer@0: or as nested listed for multidimensional arrays using the arr/1 functor, samer@0: where the innermost nesting corresponds to the FIRST Matlab dimensions samer@0: samer@0: ?- ??arr([1,2,3]). samer@0: >> ans = samer@0: 1 samer@0: 2 samer@0: 3 samer@0: samer@0: ?- ??arr([[1,2],[3,4]]). samer@0: >> ans = samer@0: 1 3 samer@0: 2 4 samer@0: samer@0: Cell arrays can be specified in a similar way using braces or the cell/1 functor. samer@0: samer@0: samer@0: To help with accessing array elements, see the Matlab functions general/paren, samer@0: general/row, and general/col in the matlab directory. samer@0: samer@0: samer@0: samer@0: *** Return values samer@0: samer@0: The results of computations can handled in several ways: samer@0: samer@0: 1. Keep the result in a Matlab workspace variable in the engine's memory samer@0: space. The names of these variables are allocated automatically samer@0: and stored in a Prolog atom. The atoms have a garbage collection samer@0: callback which means that the Matlab workspace variable is deleted samer@0: if the Prolog atom goes out of scope. samer@0: samer@0: ?- A===2+2, ??disp(A). samer@0: >> 4 % matlab textual output samer@0: samer@0: A = ws() % Prolog blob pointing to Matlab variable t_2311 samer@0: samer@0: samer@0: samer@0: 2. Convert the result to a prolog atom or term. The type of the resulting samer@0: prolog term depends on the *right hand side* of the ===/2 operator: samer@0: samer@0: ?- int(A)===2+2. samer@0: A = 4 samer@0: samer@0: ?- float(A)===2+2. samer@0: A = 4.0 samer@0: samer@0: samer@0: There are other types for strings and atoms: samer@0: samer@0: ?- atom(A) === q(hello). % q/1 means quote as Matlab string samer@0: A = hello. samer@0: samer@0: ?- string(A) === `hello. % `/1 is shorthand for q/1 samer@0: A = "hello". samer@0: samer@0: samer@0: You can also get the result as a Matlab binary array on the Prolog side: samer@0: samer@0: ?- mx(A)===eye(4). % identity matrix samer@0: A = <#0239c3a0> % Prolog blob handle (with garbage collection) samer@0: samer@0: samer@0: I haven't completely settled on the best way of handling arrays as samer@0: self-contained Prolog terms, but you can do this: samer@0: samer@0: ?- array(A)===magic(3). samer@0: A = [[8.0, 3.0, 4.0], [1.0, 5.0, 9.0], [6.0, 7.0, 2.0]]::[[3, 3]] samer@0: samer@0: As you can see, multidimensional arrays are returned as nested lists, and the samer@0: size of the array is given after the :: as [[3,3]]. samer@0: samer@0: samer@0: 3. Store the result to a MAT file and return a Prolog term which points to the samer@0: file. The names are generated automatically. This allows for persistence samer@0: of values which are referred to by stable names that can be stored, eg samer@0: in a database: samer@0: samer@0: ?- mat(A)===fft(buffer(wavread('somefile.wav'),256,128)). samer@0: samer@0: A = mat:d0608/m48598|x % dynamically generated unique locator samer@0: samer@0: This relies on the mechanism provided by the functions in matlab/db. samer@0: A certain directory is designed the root of a 'matbase' (MAT file database). samer@0: The Matlab function dbroot returns or sets this directory: samer@0: samer@0: ?- ??dbroot. samer@0: >> samer@0: ans = samer@0: samer@0: /Users/samer/matbase samer@0: samer@0: ?- ??dbroot(q('/usr/share/lib/matbase')). % switch to shared matbase samer@0: >> samer@0: ans = samer@0: samer@0: /usr/share/lib/matbase samer@0: samer@0: In this case, the locator mat:d0608/m48598|x refers to a Matlab variable called samer@0: 'x' (it's always 'x') in the file /usr/share/lib/matbase/d0608/m48598.mat. samer@0: A new directory is created each month, and the filenames are chosen dynamically samer@0: to avoid clashes with existing files. samer@0: samer@0: samer@0: samer@0: *** Debugging/tracing samer@0: samer@0: To help with debugging, you can issue the command: samer@0: samer@19: ?- debug(plml). samer@0: samer@0: which will cause each Matlab expression to be printed in its Matlab form samer@0: before execution. samer@0: I'm afraid the best documentation is the code itself, but I do intend to samer@0: produce a manual once some of the more embarrassing aspects of system samer@0: are resolved! samer@0: samer@0: samer@0: samer@0: ------------------------------------------------------------------------- samer@0: BUILDING/INSTALLATION samer@0: samer@0: See INSTALL samer@0: samer@0: samer@0: samer@0: samer@0: ------------------------------------------------------------------------- samer@0: CHANGES samer@0: samer@0: 12/04 - Using Prolog blobs with garbage collection to handle samer@0: Matlab workspace temporary variables. Works but code is samer@0: still a little messy. Would like to unify variable handling- samer@0: the important functions are alloc, free, get, put. samer@0: Also, garbage collection seems to be rather difficult to samer@0: provoke. samer@0: samer@0: 2005-08-08 samer@0: Handle Matlab errors in mlEXEC by setting lasterr() before and samer@0: checking it after engEvalString. If we do get a Matlab samer@0: error, then throw a Prolog exception, because nothing samer@0: else is safe in general. CSR. samer@0: samer@0: 2005-08-09 samer@0: Be a little more paranoid in handling workspace variables, both samer@0: in terms of checking matlab engine error codes and for samer@0: bounds checking of our own functions such as uniquevar. samer@0: samer@0: 2005-09-26 samer@0: Added matbase_mat/1 to enumerate all mat objects actually in samer@0: the file system pointed to by the matlab function dbroot. samer@0: samer@0: 2005-11-11 samer@0: Now sending very long Matlab commands via a char array samer@0: Progress in Prolog-side mxArray support: samer@0: done: samer@0: MXINFO - get size and type of array samer@0: MXSUB2IND - convert multi-dim subscript to linear index samer@0: MXGETFLOAT - get one element of numeric array (must be real) samer@0: MXGETLOGICAL - get element of logical or 0|1 array samer@0: MXGETCELL - get sub mxArray from cell array samer@0: MXGETREALS - get reals part of all elements as flat Prolog list samer@0: MXCREATENUMERIC - create double array samer@0: MXCREATECELL - create cell array samer@0: MXCREATESTRING - create char array from string or atom samer@0: MXPUTFLOAT - write one element of double array samer@0: MXPUTFLOATS - write list of elements to double array samer@0: MXPUTCELL - put an mxArray into a cell array samer@0: MXCOPYNOGC - deep copy of array, return NON-MANAGED mx ref samer@0: MXNEWREFGC - return memory managed ref to array, ie will be GCed samer@0: to do: samer@0: Imaginary parts? samer@0: Reading list of fields from a structure samer@0: Getting cell array contents as a list samer@0: tidy up: error checking and function names samer@0: possibly reduce the amount of bounds checking to improve speed? samer@0: -> need to do proper profiling! samer@0: samer@0: 2006-11-28 samer@0: Errors generated on the matlab side (ie errors in user functions samer@0: rather than the mechanisms of this library) throw exceptions of samer@0: the form mlerror(Engine,Message) instead of just atomic messages. samer@0: samer@0: Some changes to plml.pl - see header in that file for details. samer@0: samer@0: 2008-11-25 samer@0: Moved declaration of \ operator to ops.pl samer@0: Changed interface and implementation of ml_open to use list of options. samer@0: Changed build procedure to use build script and two makefiles. samer@0: samer@0: 2010-02-25 samer@0: Replaced use of mxFree with mxDestroyArray to release resources samer@0: obtained using engGetVariable - this was causing a malloc error samer@0: in Matlab 7.9. Also replaced -nojvm with -noawt option when starting samer@0: Matlab, as -nojvm is no longer supported. Apparently they're going samer@0: to withdraw support for X11 graphics at some point. I hate them. samer@0: I'm not 'upgrading' any more. samer@0: samer@0: 2010-03-19 samer@0: Removed dependency on flists samer@0: samer@0: 2010-05-30 samer@0: Merged hostname module into utils. samer@0: samer@0: 2012-01 samer@0: Big overhaul of Prolog part to simplify and speed up. samer@0: Removed unused Matlab functions from matlab/general. samer@0: Version 1! samer@0: samer@15: 2012-02 samer@15: Some changes to mlExec and mlWSAlloc to get around a problem that samer@15: was bedevilling the triserver project, which was using a Matlab samer@15: engine in a separate thread, receiving requests on a message queue, samer@15: and calling this library with call_with_time_limit. Something to samer@15: do with this combination (threads, signals etc) was causing the samer@15: Prolog system to lock up hard on returning to Prolog system samer@15: after a failing call to engGetVariable (which was getting stuck and samer@15: timing out). samer@15: samer@15: I still don't know what causes the lock-up, but I was able to detect samer@15: some signs that it was coming *before* calling engGetVariable by looking samer@15: at the Matlab engine output buffer. samer@15: samer@15: Eventual work around is not to use engGetVariable to get uniquevar samer@15: variable names (mlWSAlloc) or lasterr (mlExec) at all, but simply samer@15: to scrape these out of the output buffer. This seems to be a few samer@15: milliseconds faster too. samer@15: samer@15: other changes: removed ml_debug/1 - would rather have a flag to samer@15: enable printfs in the C++ source. samer@15: samer@18: 2012-02-06 samer@18: Yes, finally, got to the bottom of the locking up bug. samer@18: The problem was mutlithreaded access to the Matlab engine even when samer@18: making explicit calls only in one thread IF this is a secondary thread. samer@18: The reason? Garbage collection in the main thread. Have now added samer@18: a mutex to protect the Matlab engine (currently one global mutex). samer@18: If garbage collector is run on a workspace variable blob while the samer@18: mutex is locked, it fails immediately without blocking. The blob samer@18: is not reclaimed but will be can be reclaimed the next time gc is run. samer@18: samer@19: 2012-02-09 samer@19: Now using library(debug) for debugging messages. Added more helpful samer@19: message if foreign library fails to load. samer@19: samer@0: ------------------------------------------------------------------------- samer@0: ACKNOWLEDGMENTS samer@0: samer@0: This work was partially supported by UK EPSRC grants GR/S84750/01 and samer@0: GR/S82213/01. samer@0: