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).
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) :- , (true ; once(Undo), fail). 119usergoal_expansion( bt_call(Do,Undo), (Do, (true; once(Undo), fail))).
V[$X] = V[Y]
if pl2ml_hook(X,Y)
.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.
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))).
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 ).
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).
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).
257ml_test(Id,X) :- ml_eval(Id,X,[bool],[1]). 258 259ml_ws_name(X,Y,Z) :- mlWSNAME(X,Y,Z).
263?? X :- ml_exec(ml,X).
267??? Q :- ml_test(ml,Q).
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 ).
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
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).
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) }, , concat(M,L,XX). 409 410plml_dcgpl2ml_hook(I,ws(A),\atm(N)) :- mlWSNAME(A,N,I). 411plml_dcgpl2ml_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).
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)})))).
469dropmat(Eng,mat(A,B)) :- db_drop(Eng,mat(A,B)).
473exportmat(Eng,mat(A,B),Dir) :- ml_exec(Eng,copyfile(dbpath(\loc(A,B)),\q(wr(Dir)))).
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).
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))
Prolog-Matlab interface
Types
ml_eng - Any atom identifying a Matlab engine.
See
plml_dcg.pl
for information about Matlab term language.*/