1/*
    2 * Part of plml: Prolog-Matlab interface
    3 * Copyright Samer Abdallah (Queen Mary University of London; UCL) 2004-2015
    4 *
    5 *	This program is free software; you can redistribute it and/or
    6 *	modify it under the terms of the GNU General Public License
    7 *	as published by the Free Software Foundation; either version 2
    8 *	of the License, or (at your option) any later version.
    9 *
   10 *	This program is distributed in the hope that it will be useful,
   11 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
   12 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   13 *	GNU General Public License for more details.
   14 *
   15 *	You should have received a copy of the GNU General Public
   16 *	License along with this library; if not, write to the Free Software
   17 *	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
   18 */
   19
   20:- module(plml_core, 
   21	[	ml_open/1      	% (+Id)
   22	,	ml_open/2      	% (+Id, +Host)
   23	,	ml_open/3      	% (+Id, +Host, +Options)
   24	,	ml_close/1      	% (+Id)
   25
   26	,	ml_exec/2    		% (+Id, +Expr)
   27	,	ml_eval/4     		% (+Id, +Expr, +Types, -Vals)
   28	,	ml_test/2      	% (+Id, +Expr)
   29   ,  ml_ws_name/3
   30   ,  leftval/3
   31
   32	,	(??)/1            % (+Expr)        ~execute Matlab expression
   33	,	(???)/1           % (+Expr)        ~test Matlab boolean expression
   34	,	(===)/2           % (-Vals,+Expr)  ~evaluate Matlab expression
   35	,	wsvar/3      		% (+WSBlob, -Name, -Id)
   36
   37	% MATBASE
   38	,	persist_item/2 	% (+Expr,-Expr)     ~ convert volatile subterms to persistent form
   39	,	matbase_mat/2     % (+Dir, -Loc)      ~ Find matbase MAT files
   40	,	dropmat/2         % (+Id, +Loc)       ~ remove MAT file from matbase
   41	,	exportmat/3 		% (+Id, +Loc, +Dir) ~ export MAT file from matbase
   42
   43	% exported after being imported from ops
   44	,	op(1100,xfx,::)	% type specification (esp for arrays)
   45   ,  op(700,xfx,===)   % variable binding/assignment in matlab query
   46   ,  op(951,fx,??)     % evaluate term as matlab
   47   ,  op(951,fx,???)    % evaluate term as matlab boolean
   48	]).   49	
   50
   51:- multifile(user:optionset/2).   52:- multifile(user:matlab_path/2).   53:- multifile(user:matlab_init/2).

Prolog-Matlab interface

Types

ml_eng - Any atom identifying a Matlab engine.

See plml_dcg.pl for information about Matlab term language.

@tbd

Use mat(I) and tmp(I) as types to include engine Id.

Clarify relationship between return values and valid Matlab denotation.

Reshape/2 array representation: reshape([ ... ],Size)
Expression language: arr(Vals,Shape,InnerFunctor) - allows efficient
representation of arrays of arbitrary things. Will require more strict
nested list form.

Deprecate old array(Vals::Type) and cell(Vals::Type) left-value syntax.

Remove I from ml_expr//2 and add to mx type?

*/

   81:- use_module(library(apply_macros)).   82:- use_module(library(dcg_codes)).   83:- use_module(library(plml_dcg)).   84
   85:- set_prolog_flag(back_quotes,symbol_char).   86:- set_prolog_flag(double_quotes,codes).   87
   88:- op(700,xfx,===). % variable binding/assignment in matlab query
   89:- op(951,fx,??).  % evaluate term as matlab
   90:- op(951,fx,???). % evaluate term as matlab boolean
   91% :- op(650,fy,`).	 % quoting things
   92% :- op(160,xf,``).	 % postfix transpose operator
   93% :- op(100,fy,@).	 % function handles
   94% :- op(200,xfy,.^). % array exponentiation
   95% :- op(410,yfx,.*). % array times
   96% :- op(410,yfx,./). % array division
   97% :- op(410,xfy,.\). % array reverse division
   98% :- op(400,xfy,\).  % matrix reverse division
   99% :- op(100,yfx,#).  % field indexing (note left-associativity)
  100
  101:- dynamic current_engine/1.  102
  103% NB: Loading Matlab library can change LANG in environment,
  104% so we have to remember what it was and restore it after loading.
  105% See also mlOpen: we are going to talk to Matlab via UTF-8 strings.
  106:- getenv('LANG',Lang), nb_setval(plml_env_lang,Lang).  107:-	use_foreign_library(foreign(plml2)).  108:- nb_getval(plml_env_lang,Lang), setenv('LANG',Lang), 
  109   nb_delete(plml_env_lang).  110
  111:- initialization(at_halt(ml_closeall)).  112
  113ml_closeall :-
  114	forall(current_engine(Id), ml_close(Id)).
  115
  116
  117% from utils.pl
  118bt_call(Do,Undo)  :- Do, (true ; once(Undo), fail).
  119user:goal_expansion( bt_call(Do,Undo), (Do, (true;	once(Undo), fail))).
 matlab_init(-Key, -Cmd:ml_expr) is nondet
Each user-defined clause of matlab_init/2 causes Cmd to be executed whenever a new Matlab session is started.
 matlab_path(-Key, -Path:list(atom)) is nondet
Each user-defined clause of matlab_path/2 causes the directories in Path to be added to the Matlab path of every new Matlab session. Directories are relative to the root directory where padd.m is found.
 pl2ml_hook(+X:term, -Y:ml_expr) is nondet
Clauses of pl2ml_hook/2 allow for extensions to the Matlab expression language such that V[$X] = V[Y] if pl2ml_hook(X,Y).
 ml_open(+Id:ml_eng, +Host:atom, +Options:list(_)) is det
 ml_open(+Id:ml_eng, +Host:atom) is det
 ml_open(+Id:ml_eng) is det
Start a Matlab session on the given host. If Host=localhost or the name of the current current host as returned by hostname/1, then a Matlab process is started directly. Otherwise, it is started remotely via SSH. Options defaults to []. Host defaults to localhost.

Start a Matlab session on the specified host using default options. If Host is not given, it defaults to localhost. Session will be associated with the given Id, which should be an atom. See ml_open/3.

Valid options are below. Note that matlab is always called with the -nodesktop and -nosplash options.

noinit
If present, do not run initialisation commands specified by matlab_path/2 and matlab_init/2 clauses. Otherwise, do run them.
debug(In, Out)
if present, Matlab is started in a script which captures standard input and output to files In and Out respectively. (tbd)
cmd(Cmd:atom)
Call Cmd as the matlab executable. Default is 'matlab' (i.e. search for matlab on the PATH). Can be used to select a different executable or to add command line options.
awt(Flag:bool)
If false (default), call Matlab with -noawt option. Otherwise, Java graphics will be available.
  166ml_open(Id) :- ml_open(Id,localhost,[]). 
  167ml_open(Id,Host) :- ml_open(Id,Host,[]).
  168ml_open(Id,Host,Options) :- 
  169	ground(Id),
  170   pack_dir(PackDir), % needed to locate package files
  171	options_flags(Options,Flags),
  172	option(cmd(Bin),Options,matlab),
  173	(	(Host=localhost;hostname(Host))
  174	-> format(atom(Exec),'exec ~w',[Bin]) % using exec fixes Ctrl-C bug 
  175	;	format(atom(Exec),'ssh ~w ~w',[Host,Bin])
  176	),
  177	(	member(debug(In,Out),Options)
  178	-> debug(plml,'Running Matlab with protocol logging.',[]),
  179		debug(plml,'| Prolog > Matlab logged to "~w"',[In]),
  180		debug(plml,'| Prolog < Matlab logged to "~w"',[Out]),
  181		absolute_file_name(PackDir/scripts/logio,Spy,[access(execute)]),
  182		format(atom(Exec1),'~w ~w ~w ~w',[Spy,In,Out,Exec])
  183	;	Exec1=Exec
  184	),
  185	format(atom(Cmd),'~w ~w',[Exec1,Flags]),
  186	debug(plml,'About to start Matlab with: ~w',[Cmd]),
  187	mlOPEN(Cmd,Id),
  188   getenv('LANG',Lang),
  189	debug(plml,'Setting LANG to ~w and character set to UTF-8.',[Lang]),
  190   ml_exec(Id,hide(feature(`'DefaultCharacterSet',`'UTF-8'))),
  191   ml_exec(Id,hide(setenv(`'LANG',`Lang))),
  192   directory_file_path(PackDir,matlab,MatlabDir),
  193   ml_exec(Id,addpath(q(MatlabDir))),
  194   expand_file_name('~/var/matbase',[DBROOT]),
  195   debug(plml,'Setting MATBASE root to ~q.',[DBROOT]),
  196   ml_exec(Id,dbroot(q(DBROOT))),
  197
  198	assert(current_engine(Id)),
  199	(	member(noinit,Options) -> true
  200	;	forall( matlab_path(_,Dir), maplist(nofail(addpath),Dir)),
  201		forall( matlab_init(_,Cmd), nofail(Cmd))
  202	).
  203					                                                                   
  204pack_dir(PackDir) :-
  205   module_property(plml_core,file(ThisFile)),
  206   file_directory_name(ThisFile,PrologDir), 
  207   file_directory_name(PrologDir,PackDir). 
  208
  209addpath(local(D)) :- !, ml_exec(ml,padl(q(D))).
  210addpath(D) :- !, ml_exec(ml,padd(q(D))).
 ml_close(+Id:ml_eng) is det
Close Matlab session associated with Id.
  214ml_close(Id) :- ground(Id), mlCLOSE(Id), retract(current_engine(Id)).
  215
  216nofail(P) :- catch(ignore(call(P)), E, print_message(warning,E)).
  217nofail(P,X) :- catch(ignore(call(P,X)), E, print_message(warning,E)).
  218
  219options_flags(Opts,Flags) :-
  220	option(awt(AWT),Opts,false),
  221	(	AWT=true 
  222	-> Flags='-nodesktop -nosplash'
  223	;	Flags='-nodesktop -nosplash -noawt'
  224	).
 ml_exec(+Id:ml_eng, +Expr:ml_expr) is det
Execute Matlab expression without returning any values.
  230ml_exec(Id,X)  :- 
  231	debug(plml,'plml:ml_exec term ~W',[X,[max_depth(10)]]),
  232	term_mlstring(Id,X,C), !, 
  233	debug(plml(commands),'plml:ml_exec>> ~s',[C]),
  234	mlEXEC(Id,C).
 ml_eval(+Id:ml_eng, +Expr:ml_expr, +Types:list(type), -Res:list(ml_val)) is det
Evaluate Matlab expression binding return values to results list Res. This new form uses an explicit output types list, so Res can be completely unbound on entry even when multiple values are required.
  241ml_eval(Id,X,Types,Vals) :-
  242	maplist(alloc_ws(Id),Types,Vars,Names), 
  243	ml_exec(Id,hide(atom_list(Names)=X)), 
  244	maplist(convert_ws,Types,Vars,Vals).
  245
  246% alternative approach, hopefully faster
  247ml_eval_alt(Id,X,Types,Vals) :-
  248   length(Vals,N),
  249	ml_exec(Id,[p__rets=cell(1,N);cref(p__rets,[1:N])]=X;map(@uniquevar,__rets)), 
  250   % now extract ws var names from output and attach to ws blobs in Vals
  251	time(maplist(convert_ws,Types,Vars,Vals)).
  252
  253alloc_ws(I,_,Z,N) :- mlWSALLOC(I,Z), mlWSNAME(Z,N,I).
 ml_test(+Id:ml_eng, +X:ml_expr(bool)) is semidet
Succeeds if X evaluates to true in Matlab session Id.
  257ml_test(Id,X)   :- ml_eval(Id,X,[bool],[1]).
  258
  259ml_ws_name(X,Y,Z) :- mlWSNAME(X,Y,Z).
 ??(X:ml_expr(_)) is det
Execute Matlab expression X as with ml_exec/2, without returning any values.
  263?? X    :- ml_exec(ml,X).
 ???(X:ml_expr(bool)) is semidet
Evaluate Matlab boolean expression X as with ml_test/2.
  267??? Q   :- ml_test(ml,Q).
 ===(Y:ml_vals(A), X:ml_expr(A)) is det
Evaluate Matlab expression X as in ml_eval/4, binding one or more return values to Y. If Y is unbound or a single ml_val(_), only the first return value is bound. If Y is a list, multiple return values are processed.
  274Y === X :- 
  275	(	is_list(Y) 
  276	-> maplist(leftval,Y,TX,VX), ml_eval(ml,X,TX,VX)
  277	;	leftval(Y,T,V), ml_eval(ml,X,[T],[V])
  278	).
 leftval(+TVal:tagged(T), -T:type, -Val:T) is det
True if TVal is a tagged value whos type is T and value is Val.
  282leftval( ws(X),     ws,    ws(X)).    
  283leftval( mx(X),     mx,    mx(X)).
  284leftval( float(X),  float, X).
  285leftval( int(X),    int,   X).
  286leftval( bool(X),   bool,  X).
  287leftval( atom(X),   atom,  X).
  288leftval( term(X),   term,  X).
  289leftval( string(X), string,X).
  290leftval( mat(X),    mat,   X).
  291leftval( tmp(X),    tmp,   X).
  292leftval( loc(X),    loc,   X).
  293leftval( wsseq(X),  wsseq, wsseq(X)).
  294leftval( list(T,X), list(T), X).
  295leftval( array(X::[Size->Type]),  array(Type,Size), X) :- !.
  296leftval( array(X::[Size]),        array(float,Size), X) :- !.
  297leftval( cell(X::[Size->Type]),   cell(Type,Size),  X) :- !.
  298leftval( cell(X::[Size]),         cell(mx,Size),  X) :- !.
  299leftval( Val:Type,  Type,  Val).
  300
  301
  302% Once the computation has been done, the MATLAB workspace contains
  303% the results which must be transferred in the appropriate form the
  304% specified left-values, in one of several forms, eg mxArray pointer,
  305% a float, an atom, a string or a locator. 
  306%
  307% Note that requesting a locator causes a further call
  308% to MATLAB to do a dbsave.
  309%
  310% If no type requestor tag is present, then a unique variable name
  311% is generated to store the result in the Matlab workspace. This name
  312% is returned in the variable as a ws blob.
  313% The idea is to avoid unnecessary traffic over the Matlab engine pipe.
  314
  315% conversion between different representations of values
  316% !! FIXME: check memory management of mxArrays here
 convert_ws(+Type:type, +In:ws_blob, -Out:Type) is det
Convert value of Matlab workspace variable to representation determined by Type.
  322convert_ws(ws, Z, ws(Z)) :- !.
  323convert_ws(wsseq, Z, wsseq(Z)) :- !.
  324convert_ws(mx, Z, mx(Y)) :- !, mlWSGET(Z,Y). 
  325
  326% conversions that go direct from workspace variables to matbase.
  327convert_ws(tmp, Z, Y) :- !, mlWSNAME(Z,_,I), bt_call(db_tmp(I,ws(Z),Y), db_drop(I,Y)).
  328convert_ws(mat, Z, Y) :- !, mlWSNAME(Z,_,I), bt_call(db_save(I,ws(Z),Y), db_drop(I,Y)).
  329
  330% return cell array as list of temporary or permanent mat file locators
  331% (this avoids getting whole array from WS to MX).
  332convert_ws(cell(tmp,Size), Z, L) :- !, 
  333	mlWSNAME(Z,_,I),
  334	bt_call(db_tmp_all(I,ws(Z),L,Size), db_drop_all(I,L,Size)).
  335
  336convert_ws(cell(mat,Size), Z, L) :- !, 
  337	mlWSNAME(Z,_,I),
  338	bt_call(db_save_all(I,ws(Z),L,Size), db_drop_all(I,L,Size)).
  339
  340% Most other conversions from ws(_) go via mx(_)
  341convert_ws(T,Z,A) :- 
  342	mlWSGET(Z,X), 
  343	convert_mx(T,X,A).
 convert_mx(+Type:type, +In:mx_blob, -Out:Type) is det
Convert value of in-process Matlab array In to representation determined by Type.
  349convert_mx(atom,   X, Y) :- !, mlMX2ATOM(X,Y).
  350convert_mx(bool,   X, Y) :- !, mlMX2LOGICAL(X,Y).
  351convert_mx(float,  X, Y) :- !, mlMX2FLOAT(X,Y).
  352convert_mx(int,    X, Y) :- !, mlMX2FLOAT(X,Z), Y is truncate(Z).
  353convert_mx(string, X, Y) :- !, mlMX2STRING(X,Y).
  354convert_mx(term,   X, Y) :- !, mlMX2ATOM(X,Z), term_to_atom(Y,Z).
  355convert_mx(loc,    X, mat(Y,W)) :- !, mlMX2ATOM(X,Z), term_to_atom(Y|W,Z).
  356
  357convert_mx(mat,    X, Y) :- !, % !!! use first engine to save to its matbase
  358	current_engine(I),  
  359	bt_call( db_save(I,mx(X),Y), db_drop(I,Y)).
  360convert_mx(tmp,    X, Y) :- !, % !!! use first engine to save to its matbase
  361	current_engine(I),  
  362	bt_call( db_tmp(I,mx(X),Y), db_drop(I,Y)).
  363
  364convert_mx(list(float), X, Y) :- !, mlGETREALS(X,Y). 
  365
  366convert_mx(cell(Type,Size), X, L) :- !, 
  367	mx_size_type(X,Size,cell),
  368	prodlist(Size,1,Elems), % total number of elements
  369	mapnats(conv_cref(Type,X),Elems,[],FL),
  370	reverse(Size,RSize),
  371	unflatten(RSize,FL,L).
  372
  373convert_mx(array(Type,Size), X, L) :- !, 
  374	mx_size_type(X,Size,MXType),
  375	compatible(MXType,Type),
  376	prodlist(Size,1,Elems), % total number of elements
  377	mapnats(conv_aref(Type,X),Elems,[],FL),
  378	reverse(Size,RSize),
  379	unflatten(RSize,FL,L).
  380
  381compatible(double,float).
  382compatible(double,int).
  383compatible(double,bool).
  384compatible(logical,float).
  385compatible(logical,int).
  386compatible(logical,bool).
  387
  388% !! Need to worry about non gc mx atoms
  389conv_aref(bool,  X,I,Y) :- !, mlGETLOGICAL(X,I,Y). 
  390conv_aref(float, X,I,Y) :- !, mlGETFLOAT(X,I,Y). 
  391conv_aref(int,   X,I,Y) :- !, mlGETFLOAT(X,I,W), Y is truncate(W). 
  392
  393conv_cref(mx,Z,I,Y) :- !, mlGETCELL(Z,I,Y). % !! non gc mx
  394conv_cref(Ty,Z,I,Y) :- !, conv_cref(mx,Z,I,X), convert_mx(Ty,X,Y).
  395
  396%convert(W, field(Z,N,I)) :- convert(mx(X),Z), mlGETFIELD(X,I,N,Y), convert_mx(W,Y). 
  397%convert(W, field(Z,N))   :- convert(mx(X),Z), mlGETFIELD(X,1,N,Y), convert_mx(W,Y). 
  398
  399% Utilities used by convert/2
  400
  401mapnats(P,N,L1,L3) :- succ(M,N), !, call(P,N,PN), mapnats(P,M,[PN|L1],L3).
  402mapnats(_,0,L,L) :- !.
  403
  404prodlist([],P,P).
  405prodlist([X1|XX],P1,P3) :- P2 is P1*X1, prodlist(XX,P2,P3).
  406
  407concat(0,_,[]) --> !, [].
  408concat(N,L,[X1|XX]) --> { succ(M,N), length(X1,L) }, X1, concat(M,L,XX).
  409
  410plml_dcg:pl2ml_hook(I,ws(A),\atm(N)) :- mlWSNAME(A,N,I).
  411plml_dcg:pl2ml_hook(I,mx(X),$ws(Z)) :- mlWSALLOC(I,Z), mlWSPUT(Z,X).
  412
  413
  414% convert a flat list into a nested-list array representation 
  415% using given size specification
  416unflatten([N],Y,Y) :- !, length(Y,N).
  417unflatten([N|NX],Y,X) :-
  418	length(Y,M),
  419	L is M/N, integer(L), L>=1,
  420	phrase(concat(N,L,Z),Y),
  421	maplist(unflatten(NX),Z,X).
  422
  423% thin wrappers
  424mx_size_type(X,Sz,Type) :- mlMXINFO(X,Sz,Type).
  425mx_sub2ind(X,Subs,Ind) :- mlSUB2IND(X,Subs,Ind).
  426
  427
  428% these create memory managed arrays, which are not suitable
  429% for putting into a cell array
  430
  431% roughly, mx_create :: type -> mxarray.
  432mx_create([Size],mx(X))    :- mlCREATENUMERIC(Size,Z), mlNEWREFGC(Z,X).
  433mx_create({Size},mx(X))    :- mlCREATECELL(Size,Z), mlNEWREFGC(Z,X).
  434mx_string(string(Y),mx(X)) :- mlCREATESTRING(Y,Z), mlNEWREFGC(Z,X).
  435
  436% MX as MUTABLE variables
  437mx_put(aref(mx(X),I),float(Y)) :- mlPUTFLOAT(X,I,Y).
  438mx_put(cref(mx(X),I),mx(Y))    :- mlPUTCELL(X,I,Y). % !! ensure that Y is non gc
  439mx_put(mx(X),list(float,Y))    :- mlPUTFLOATS(X,1,Y). 
 wsvar(+X:ws_blob(A), -Nm:atom, -Id:ml_eng) is semidet
True if X is a workspace variable in Matlab session Id. Unifies Nm with the name of the Matlab variable.
  444wsvar(A,Name,Engine) :- mlWSNAME(A,Name,Engine).
  445
  446/* __________________________________________________________________________________
  447 * Dealing with the Matbase
  448 *
  449 * The Matbase is a file system tree which contains lots of
  450 * MAT files which have been created by using the dbsave
  451 * Matlab function.
  452 */
  453
  454
  455% saving and dropping matbase files
  456db_save(I,Z,Y)   :- ml_eval(I,dbsave(Z),[loc],[Y]).
  457db_tmp(I,Z,Y)    :- ml_eval(I,dbtmp(Z),[loc],[Y]).
  458db_drop(I,mat(A,B)) :- ml_exec(I,dbdrop(\loc(A,B))).
  459
  460db_save_all(I,Z,L,Size) :- ml_eval(I,dbcellmap(@dbsave,Z),[cell(loc,Size)],[L]). 
  461db_tmp_all(I,Z,L,Size)  :- ml_eval(I,dbcellmap(@dbtmp,Z),[cell(loc,Size)],[L]). 
  462db_drop_all(I,L,Size)   :- 
  463	length(Size,Dims), 
  464	ml_exec(I,hide(foreach(@dbdrop,arr(Dims,L,X\\{loc(X)})))).
 dropmat(+Id:ml_id, +Mat:ml_loc) is det
Deleting MAT file from matbase.
  469dropmat(Eng,mat(A,B))       :- db_drop(Eng,mat(A,B)).
 exportmat(+Id:ml_id, +Mat:ml_loc, +Dir:atom) is det
Export specified MAT file from matbase to given directory.
  473exportmat(Eng,mat(A,B),Dir) :- ml_exec(Eng,copyfile(dbpath(\loc(A,B)),\q(wr(Dir)))).
 matbase_mat(+Id:ml_eng, -X:ml_loc) is nondet
Listing mat files actually in matbase at given root directory.
  477matbase_mat(Id,mat(SubDir/File,x)) :-
  478	ml_eval(Id,[dbroot,q(/)],[atom],[DBRoot]), % NB with trailing slash
  479
  480	atom_concat(DBRoot,'*/d*',DirPattern),
  481	expand_file_name(DirPattern,Dirs),
  482	member(FullDir,Dirs), 
  483	atom_concat( DBRoot,SubDirAtom,FullDir),
  484	term_to_atom(SubDir,SubDirAtom),
  485	atom_concat(FullDir,'/m*.mat',FilePattern),
  486	expand_file_name(FilePattern,Files),
  487	member(FullFile,Files),
  488	file_base_name(FullFile,FN),
  489	atom_concat(File,'.mat',FN).
 persist_item(+X:ml_expr(A), -Y:ml_expr(A)) is det
Convert Matlab expression to persistent form not dependent on current Matlab workspace or MX arrays in Prolog memory space. Large values like arrays and structures are saved in the matbase replaced with matbase locators. Scalar values are converted to literal numeric values. Character strings are converted to Prolog atoms. Cell arrays wrapped in the wsseq/1 functor are converted to literal form.

NB. any side effects are undone on backtracking -- in particular, any files created in the matbase are deleted.

  503persist_item($T,$T) :- !.
  504persist_item(mat(A,B),mat(A,B)) :- !.  
  505
  506persist_item(ws(A),B) :- !,
  507	mlWSNAME(A,_,Eng), 
  508	ml_eval(Eng,typecode(ws(A)),[int,bool,bool],[Numel,IsNum,IsChar]),
  509	(	Numel=1, IsNum=1
  510	->	convert_ws(float,A,B) 
  511	;	IsChar=1
  512	-> convert_ws(atom,A,AA), B= `AA
  513	;	convert_ws(mat,A,B)
  514	).
  515
  516
  517% !! TODO - 
  518%     deal with collections - we can either save the aggregate
  519%     OR save the elements individually and get a prolog list of the
  520%     locators.
  521persist_item(wsseq(A),cell(B)) :- 
  522	mlWSNAME(A,_,Eng), 
  523	ml_test(Eng,iscell(ws(A))), 
  524	ml_eval(Eng,wsseq(A),[cell(mat,_)],[B]).
  525
  526persist_item(mx(X),B) :- 
  527	mx_size_type(X,Size,Type),
  528	(	Size=[1], Type=double
  529	->	convert_mx(float,X,B) 
  530	;	Type=char
  531	-> convert_mx(atom,X,AA), B= `AA
  532	;	convert_mx(mat,X,B)
  533	).
  534
  535persist_item(A,A)   :- atomic(A).
  536
  537
  538prolog:message(ml_illegal_expression(Expr),[ 'Illegal Matlab expression: ~w'-[Expr] | Z], Z).
  539prolog:message(mlerror(Eng,Msg,Cmd),[
  540'Error in Matlab engine (~w):\n   * ~w\n   * while executing "~w"'-[Eng,Msg,Cmd] | Z], Z).
  541
  542hostname(H) :-
  543   (  getenv('HOSTNAME',H) -> true
  544   ;  read_line_from_pipe(hostname,H)
  545   ).
  546
  547read_line_from_pipe(Cmd,Atom) :-
  548   setup_call_cleanup(
  549      open(pipe(Cmd),read,S),
  550      (read_line_to_codes(S,Codes), atom_codes(Atom,Codes)),
  551      close(S))