19
20:- module(plml,
21 [ ml_open/1 22 , ml_open/2 23 , ml_open/3 24 , ml_close/1 25
26 , ml_exec/2 27 , ml_eval/4 28 , ml_test/2 29 , ml_ws_name/3
30 , leftval/3
31
32 , (??)/1 33 , (???)/1 34 , (===)/2 35
36 , term_mlstring/3 37 , term_texatom/2 38 , wsvar/3 39
40 41 , persist_item/2 42 , matbase_mat/2 43 , dropmat/2 44 , exportmat/3 45
46
47 48 , compileoptions/2
49 , multiplot/2
50 , mhelp/1
51
52 , op(650,fy,'`') 53 , op(160,xf,'``') 54 , op(100,fy,@) 55
56 57 58 , op(210,xfy,.^) 59 , op(410,yfx,.*) 60 , op(410,yfx,./) 61 , op(410,xfy,.\) 62 , op(400,xfy,\) 63 , op(700,xfx,===) 64 , op(700,xfx,:==) 65 , op(951,fx,??) 66 , op(951,fx,???) 67 , op(100,yfx,#) 68 , op(750,fy,\\) 69 , op(750,xfy,\\) 70
71 72 , op(1100,xfx,::) 73 , op(550,xfx,..) 74 ]). 75
76
77:- multifile(user:optionset/2). 78:- multifile(user:matlab_path/2). 79:- multifile(user:matlab_init/2). 80:- multifile(user:pl2ml_hook/2).
234:- use_module(library(apply_macros)). 235:- use_module(library(dcg_core)). 236:- use_module(library(dcg_codes),except([q//1])). 237
238:- set_prolog_flag(back_quotes,symbol_char). 239:- set_prolog_flag(double_quotes,codes). 240
241:- op(700,xfx,===). 242:- op(951,fx,??). 243:- op(951,fx,???). % evaluate term as matlab boolean
244:- op(650,fy,`). % quoting things
245:- op(160,xf,``). % postfix transpose operator
246:- op(100,fy,@). % function handles
247:- op(200,xfy,.^). % array exponentiation
248:- op(410,yfx,.*). % array times
249:- op(410,yfx,./). % array division
250:- op(410,xfy,.\). % array reverse division:- op(400,xfy,\). % matrix reverse division:- op(100,yfx,#). % field indexing (note left-associativity):- dynamic current_engine/1.read_line_from_pipe(Cmd,Atom) :- setup_call_cleanup( open(pipe(Cmd),read,S), (read_line_to_codes(S,Codes), atom_codes(Atom,Codes)), close(S)).% NB: Loading Matlab library can change LANG in environment,% so we have to remember what it was and restore it after loading.% See also mlOpen: we are going to talk to Matlab via UTF-8 strings.:- (getenv('LANG',Lang) -> LL=just(Lang); LL=nothing), nb_setval(plml_env_lang,LL).:- use_foreign_library(foreign(plml)).:- nb_getval(plml_env_lang,LL), nb_delete(plml_env_lang), (LL=just(Lang) -> setenv('LANG',Lang); unsetenv('LANG')).:- initialization(at_halt(ml_closeall)).ml_closeall :- forall(current_engine(Id), ml_close(Id)).% from utils.plbt_call(Do,Undo) :- Do, (true ; once(Undo), fail).user: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.% * stderr(S:{share|redirect(path)})% Determines what happens to Matlab's standard error output. If =|share|=,% then it is merged with SWI Prolog's standard error. If =|redirect(Path)|=,% it is sent to the named file. Default is =|share|=.% * path(Dirs:list(filespec))% Another mechanism for determining the Matlab path. Items are expanded% using absolute_file_name/2. Default is [].ml_open(Id) :- ml_open(Id,localhost,[]).ml_open(Id,Host) :- ml_open(Id,Host,[]).ml_open(Id,Host,Options) :- ground(Id), pack_dir(PackDir), option(cmd(Bin),Options,matlab), option(stderr(StdErr),Options,share), option(awt(AWT),Options,false), must_be(boolean,AWT), option(path(Path),Options,[]), must_be(list,Path), once(seqmap(build,[flags,awt(AWT),host(Host),stderr(StdErr),debug(PackDir,Options),exec],Bin,Exec)), debug(plml,'About to start Matlab with: ~q',[Exec]), setup_call_catcher_cleanup( mlOPEN(Exec,Id), ml_init(Id,PackDir,Path), exception(_), mlCLOSE(Id) ), assert(current_engine(Id)), expand_file_name('~/var/matbase',[DBROOT]), debug(plml,'Setting MATBASE root to ~q.',[DBROOT]), nofail(ml_exec(Id,dbroot(q(DBROOT)))), ( member(noinit,Options) -> true ; forall( matlab_path(_,Dir), maplist(nofail(addpath(Id)),Dir)), forall( matlab_init(_,Exec), nofail(Exec)) ).% this is the part of the initialisation that is not allowed to failml_init(Id,PackDir,Path) :- directory_file_path(PackDir,matlab,MatlabDir), ( getenv('LANG',Lang) -> true ; Lang='UTF-8', print_message(warning,no_lang(Lang)) ), debug(plml,'Setting LANG to ~w and character set to UTF-8.',[Lang]), ml_exec(Id,hide(feature(`'DefaultCharacterSet',`'UTF-8'))), ml_exec(Id,hide(setenv(`'LANG',`Lang))), debug(plml,'Adding ~q to Matlab path.',[MatlabDir]), ml_exec(Id,addpath(`MatlabDir)), forall( member(Spec,Path), ( absolute_file_name(Spec,Dir,[file_type(directory)]), ml_exec(Id,addpath(q(Dir))))).
378build(flags(Flags)) --> wappend([Flags]).
379build(host(localhost)) --> !.
380build(host(Host)) --> {hostname(Host)}, !.
381build(host(Host)) --> prepend([ssh,Host|T],T).
382build(stderr(share)) --> [].
383build(stderr(discard)) --> build(stderr(redirect('/dev/null'))).
384build(stderr(redirect(Dest)),Cmd1,Cmd2) :-
385 format(string(Cmd2),'sh -c "exec ~w 2>~w"',[Cmd1,Dest]).
386build(debug(PackDir,Options)) -->
387 { member(debug(In,Out),Options), !,
388 debug(plml,'Running Matlab with protocol logging.',[]),
389 debug(plml,'| Prolog > Matlab logged to "~w"',[In]),
390 debug(plml,'| Prolog < Matlab logged to "~w"',[Out]),
391 absolute_file_name(PackDir/scripts/logio,Spy,[access(execute)])
392 },
393 prepend([Spy,In,Out|T],T).
394build(debug(_,_)) --> [].
395build(exec) --> prepend([exec|T],T).
396build(flags) --> wappend(['-nodesktop','-nosplash']).
397build(awt(false)) --> wappend(['-noawt']).
398build(awt(true)) --> [].
399
400prepend(Words,[S1],S1,S2) :- concat(Words,S2).
401wappend(Words,S1,S2) :- concat([S1|Words],S2).
402concat(Words,String) :- atomics_to_string(Words,' ',String).
403
404
405pack_dir(PackDir) :-
406 module_property(plml,file(ThisFile)),
407 file_directory_name(ThisFile,PrologDir),
408 file_directory_name(PrologDir,PackDir).
409
410addpath(Id,local(D)) :- !, ml_exec(Id,padl(q(D))).
411addpath(Id,D) :- !, ml_exec(Id,padd(q(D))).
415ml_close(Id) :- ground(Id), mlCLOSE(Id), retract(current_engine(Id)).
416
417nofail(P) :- catch(ignore(call(P)), E, print_message(warning,E)).
418nofail(P,X) :- catch(ignore(call(P,X)), E, print_message(warning,E)).
424ml_exec(Id,X) :-
425 debug(plml,'plml:ml_exec term ~W',[X,[quoted(true),max_depth(10)]]),
426 term_mlstring(Id,X,C), !,
427 debug(plml(commands),'plml:ml_exec>> ~s',[C]),
428 mlEXEC(Id,C).
435ml_eval(Id,X,Types,Vals) :-
436 maplist(alloc_ws(Id),Types,Vars),
437 ml_exec(Id,hide(wsx(Vars)=X)),
438 maplist(convert_ws,Types,Vars,Vals).
439
440alloc_ws(I,_,Z) :- mlWSALLOC(I,Z).
444ml_test(Id,X) :- ml_eval(Id,X,[bool],[1]).
445
446ml_ws_name(X,Y,Z) :- mlWSNAME(X,Y,Z).
453Y === X :-
454 current_engine(Id),
455 ( is_list(Y)
456 -> maplist(leftval,Y,TX,VX), ml_eval(Id,X,TX,VX)
457 ; leftval(Y,T,V), ml_eval(Id,X,[T],[V])
458 ).
462leftval( ws(X), ws, ws(X)).
463leftval( mx(X), mx, mx(X)).
464leftval( float(X), float, X).
465leftval( int(X), int, X).
466leftval( bool(X), bool, X).
467leftval( atom(X), atom, X).
468leftval( term(X), term, X).
469leftval( string(X), string,X).
470leftval( mat(X), mat, X).
471leftval( tmp(X), tmp, X).
472leftval( loc(X), loc, X).
473leftval( wsseq(X), wsseq, wsseq(X)).
474leftval( list(T,X), list(T), X).
475leftval( arr(X), array(_,_), X).
476leftval( array(X::[Size->Type]), array(Type,Size), X) :- !.
477leftval( array(X::[Size]), array(float,Size), X) :- !.
478leftval( cell(X::[Size->Type]), cell(Type,Size), X) :- !.
479leftval( cell(X::[Size]), cell(mx,Size), X) :- !.
480leftval( Val:Type, Type, Val).
486?? X :- current_engine(Id), ml_exec(Id,X).
491??? Q :- current_engine(Id), ml_test(Id,Q).
492
493
505
506
508stmt(I,hide(A)) --> !, stmt(I,A), ";".
509stmt(I,(A;B)) --> !, stmt(I,A), ";", stmt(I,B).
510stmt(I,(A,B)) --> !, stmt(I,A), ",", stmt(I,B).
511stmt(I,A=B) --> !, ml_expr(I,A), "=", ml_expr(I,B).
512stmt(I,if(A,B)) --> !, "if ",ml_expr(I,A), ", ", stmt(I,B), ", end".
513stmt(I,if(A,B,C)) --> !, "if ",ml_expr(I,A), ", ", stmt(I,B), ", else ", stmt(I,C), ", end".
514stmt(I,Expr) --> !, ml_expr(I,Expr).
519ml_expr(_,\X) --> !, phrase(X).
520ml_expr(I,$X) --> !, {pl2ml_hook(X,Y)}, ml_expr(I,Y).
521ml_expr(I,q(X)) --> !, ({string(X)} -> q(str(X)); q(stmt(I,X))).
522ml_expr(_,tq(X)) --> !, q(pl2tex(X)).
523ml_expr(_,atom(X)) --> !, atm(X).
524ml_expr(_,term(X)) --> !, wr(X). 525ml_expr(_,mat(X,Y)) --> !, "dbload(", loc(X,Y), ")".
526ml_expr(_,loc(L)) --> !, { L=mat(X,Y) }, loc(X,Y).
527ml_expr(I,mx(X)) --> !, { mlWSALLOC(I,Z), mlWSPUT(Z,X) }, ml_expr(I,ws(Z)).
528ml_expr(I,ws(A)) --> !, { mlWSNAME(A,N,I) }, atm(N).
529ml_expr(I,wsx([A|B])) --> !, { mlWSNAME(A,N,I) }, "[", atm(N), wsx(B), "]".
530ml_expr(I,wsseq(A)) --> !, { mlWSNAME(A,N,I) }, atm(N).
531ml_expr(_,noeval(_)) --> !, {fail}. 532
533ml_expr(_,'Infinity') --> !, "inf".
534ml_expr(_,'Nan') --> !, "nan".
535
536ml_expr(I,A+B) --> !, "plus", args(I,A,B).
537ml_expr(I,A-B) --> !, "minus", args(I,A,B).
538ml_expr(I, -B) --> !, "uminus", args(I,B).
539ml_expr(I, +B) --> !, "uplus", args(I,B).
540ml_expr(I,A^B) --> !, "mpower", args(I,A,B).
541ml_expr(I,A*B) --> !, "mtimes", args(I,A,B).
542ml_expr(I,A/B) --> !, "mrdivide", args(I,A,B).
543ml_expr(I,A\B) --> !, "mldivide", args(I,A,B).
544ml_expr(I,A.^B)--> !, "power", args(I,A,B).
545ml_expr(I,A.*B)--> !, "times", args(I,A,B).
546ml_expr(I,A./B)--> !, "rdivide", args(I,A,B).
547ml_expr(I,A.\B)--> !, "ldivide", args(I,A,B).
548ml_expr(I,A>B) --> !, "gt",args(I,A,B).
549ml_expr(I,A<B) --> !, "lt",args(I,A,B).
550ml_expr(I,A>=B)--> !, "ge",args(I,A,B).
551ml_expr(I,A=<B)--> !, "le",args(I,A,B).
552ml_expr(I,A==B)--> !, "eq",args(I,A,B).
553ml_expr(I,A:B) --> !, range(I,A,B).
554
555ml_expr(_,[]) --> !, "[]".
556ml_expr(_,{}) --> !, "{}".
557ml_expr(I,[X]) --> !, "[", matrix(v,I,X), "]".
558ml_expr(I,[X|XX]) --> !, "[", ml_expr(I,X), seqmap(do_then_call(",",ml_expr(I)),XX), "]".
559ml_expr(I,{X}) --> !, "{", matrix(_,I,X), "}".
560
561ml_expr(I, `B) --> !, q(stmt(I,B)).
562ml_expr(I,A#B) --> !, "getfield", args(I,A,q(B)).
563ml_expr(I,B``) --> !, "ctranspose", args(I,B).
564ml_expr(_,@B) --> !, "@", atm(B).
565ml_expr(I, \\B) --> !, "@()", ml_expr(I,B).
566ml_expr(I, A\\B) --> !, { term_variables(A,V), varnames(V) },
567 "@(", varlist(A), ")", ml_expr(I,B).
568ml_expr(I,lambda(A,B)) --> !, ml_expr(I,A\\B).
569ml_expr(I,thunk(B)) --> !, ml_expr(I, \\B).
570
571
572% !! This is problematic: we are using apply to represent both
573% function application and array dereferencing. For function
574% calls, A must be a function name atom or a function handle
575% If A is an array, it cannot be an expression, unless we
576% switch to using the paren Matlab function, which will be slower.
577ml_expr(I,apply(A,B)) --> !, ml_expr(I,A), arglist(I,B).
578ml_expr(I,cref(A,B)) --> !, ml_expr(I,A), "{", clist(I,B), "}".
579
580% array syntax
581ml_expr(I,arr($X)) --> !, { pl2ml_hook(X,L) }, ml_expr(I,arr(L)).
582ml_expr(I,arr(L)) --> !, { array_dims(L,D) }, array(D,I,L).
583ml_expr(I,arr(D,L)) --> !, array(D,I,L).
584ml_expr(I,arr(D,L,P)) --> !, array(D,I,P,L).
585ml_expr(I,atvector(L))--> !, "[", clist_at(I,L), "]".
586ml_expr(I,vector(L)) --> !, "[", clist(I,L), "]".
587ml_expr(I,cell(L)) --> !, "{", clist(I,L), "}".
588ml_expr(_,'$VAR'(N)) --> !, "p_", atm(N).
589
590% catch these and throw exception
591ml_expr(_,hide(A)) --> {throw(ml_illegal_expression(hide(A)))}.
592ml_expr(_,(A;B)) --> {throw(ml_illegal_expression((A;B)))}.
593ml_expr(_,(A,B)) --> {throw(ml_illegal_expression((A,B)))}.
594ml_expr(_,A=B) --> {throw(ml_illegal_expression(A=B))}.
595
596% these are the catch-all clauses which will deal with matlab names, and literals
597% should we filter on the head functor?
598ml_expr(_,A) --> {string(A)}, !, q(str(A)).
599ml_expr(_,A) --> {atomic(A)}, !, atm(A).
600ml_expr(I,F) --> {F=..[H|AX]}, atm(H), arglist(I,AX).
601
602ml_expr_with(I,Lambda,Y) --> {copy_term(Lambda,Y\\PY)}, ml_expr(I,PY).
603
604
605% take output of DCG phrase P and generate properly escaped Matlab single quoted string.
606q(P) --> {phrase(P,Codes)}, "'", esc(ml_quote,Codes), "'".
607
608ml_quote([0''|T],T) --> !, "''".
609ml_quote([0'\n|T],T) --> !, "\\n".
610ml_quote([C|T],T) --> [C].
611
612% dimensions implicit in nested list representation
613array_dims([X|_],M) :- !, array_dims(X,N), succ(N,M).
614array_dims(_,0).
615
616% efficiently output row vector of workspace variable names
617wsx([]) --> [].
618wsx([A|AX]) --> { mlWSNAME(A,N,_) }, ",", atm(N), wsx(AX).
619
620%% array(+Dims:natural, +Id:ml_eng, +Array)// is det.
621%
622% Format nested lists as Matlab multidimensional array.
623% Dims is the number of dimensions of the resulting array and
624% should equal the nesting level of Array, ie if Array=[1,2,3],
625% Dims=1; if Array=[[1,2],[3,4]], Dims=2, etc.
626array(0,I,X) --> !, ml_expr(I,X).
627array(1,I,L) --> !, "[", seqmap_with_sep(";",ml_expr(I),L), "]".
628array(2,I,L) --> !, "[", seqmap_with_sep(",",array(1,I),L), "]".
629array(N,I,L) --> {succ(M,N)}, "cat(", atm(N), ",", seqmap_with_sep(",",array(M,I),L), ")".
630
631array(0,I,P,X) --> !, ml_expr_with(I,P,X).
632array(1,I,P,L) --> !, "[", seqmap_with_sep(";",ml_expr_with(I,P),L), "]".
633array(2,I,P,L) --> !, "[", seqmap_with_sep(",",array(1,I,P),L), "]".
634array(N,I,P,L) --> {succ(M,N)}, "cat(", atm(N), ",", seqmap_with_sep(",",array(M,I,P),L), ")".
635
636matrix(h,I,(A,B)) --> !, ml_expr(I,A), ",", matrix(h,I,B).
637matrix(v,I,(A;B)) --> !, ml_expr(I,A), ";", matrix(v,I,B).
638matrix(_,I,A) --> !, ml_expr(I,A).
639
640
641% colon syntax for ranges
642range(I,A,B:C) --> !, "colon", arglist(I,[A,B,C]).
643range(I,A,B) --> !, "colon", args(I,A,B).
644
645
646%% varlist(+Term)// is det.
647% Format comma separated list of lambda expression arguments.
648varlist((A,B)) --> !, atm(A), ",", varlist(B).
649varlist(A) --> !, atm(A).
650
651
652%% clist(+Id:ml_eng, +Items:list(ml_expr))// is det.
653% Format list of Matlab expressions in a comma separated list.
654clist(_,[]) --> [].
655clist(I,[L1|LX]) --> ml_expr(I,L1), seqmap(do_then_call(",",ml_expr(I)),LX).
656
657
658%% clist_at(+Id:ml_eng, +Items:list(ml_expr))// is det.
659% Format list of atoms in a comma separated list.
660clist_at(_,[]) --> [].
661clist_at(_,[L1|LX]) --> atm(L1), seqmap(do_then_call(",",atm),LX).
662
663
664%% arglist(+Id:ml_eng, +Args:list(ml_expr))// is det.
665% DCG rule to format a list of Matlab expressions as function arguments
666% including parentheses.
667arglist(I,X) --> "(", clist(I,X), ")".
668
669
670%% args(+Id:ml_eng, +A1:ml_expr, +A2:ml_expr)// is det.
671%% args(+Id:ml_eng, +A1:ml_expr)// is det.
672%
673% DCG rule to format one or two Matlab expressions as function arguments
674% including parentheses.
675args(I,X,Y) --> "(", ml_expr(I,X), ",", ml_expr(I,Y), ")".
676args(I,X) --> "(", ml_expr(I,X), ")".
677
678
679%% atm(+A:atom)// is det.
680% DCG rule to format an atom using write/1.
681atm(A,C,T) :- format(codes(C,T),'~w',[A]).
682
683varnames(L) :- varnames(1,L).
684varnames(_,[]).
685varnames(N,[TN|Rest]) :-
686 atom_concat(p_,N,TN), succ(N,M),
687 varnames(M,Rest).
688
689
690%% term_mlstring(+Id:ml_eng,+X:ml_expr,-Y:list(code)) is det.
691% Convert term representing Matlab expression to a list of character codes.
692term_mlstring(I,Term,String) :- phrase(stmt(I,Term),String), !.
693
694%% term_texatom(+X:tex_expr,-Y:atom) is det.
695% Convert term representing TeX expression to a string in atom form.
696term_texatom(Term,Atom) :- phrase(pl2tex(Term),String), !, atom_codes(Atom,String).
697
698
699
700% Once the computation has been done, the MATLAB workspace contains
701% the results which must be transferred in the appropriate form the
702% specified left-values, in one of several forms, eg mxArray pointer,
703% a float, an atom, a string or a locator.
704%
705% Note that requesting a locator causes a further call
706% to MATLAB to do a dbsave.
707%
708% If no type requestor tag is present, then a unique variable name
709% is generated to store the result in the Matlab workspace. This name
710% is returned in the variable as a ws blob.
711% The idea is to avoid unnecessary traffic over the Matlab engine pipe.
712
713% conversion between different representations of values
714% !! FIXME: check memory management of mxArrays here
715
716
717%% convert_ws( +Type:type, +In:ws_blob, -Out:Type) is det.
718% Convert value of Matlab workspace variable to representation
719% determined by Type.
720convert_ws(ws, Z, ws(Z)) :- !.
721convert_ws(wsseq, Z, wsseq(Z)) :- !.
722convert_ws(mx, Z, mx(Y)) :- !, mlWSGET(Z,Y).
723
724% conversions that go direct from workspace variables to matbase.
725convert_ws(tmp, Z, Y) :- !, mlWSNAME(Z,_,I), bt_call(db_tmp(I,ws(Z),Y), db_drop(I,Y)).
726convert_ws(mat, Z, Y) :- !, mlWSNAME(Z,_,I), bt_call(db_save(I,ws(Z),Y), db_drop(I,Y)).
727
728% return cell array as list of temporary or permanent mat file locators
729% (this avoids getting whole array from WS to MX).
730convert_ws(cell(tmp,Size), Z, L) :- !,
731 mlWSNAME(Z,_,I),
732 bt_call(db_tmp_all(I,ws(Z),L,Size), db_drop_all(I,L,Size)).
733
734convert_ws(cell(mat,Size), Z, L) :- !,
735 mlWSNAME(Z,_,I),
736 bt_call(db_save_all(I,ws(Z),L,Size), db_drop_all(I,L,Size)).
737
738% Most other conversions from ws(_) go via mx(_)
739convert_ws(T,Z,A) :-
740 mlWSGET(Z,X),
741 convert_mx(T,X,A).
742
743
744%% convert_mx( +Type:type, +In:mx_blob, -Out:Type) is det.
745% Convert value of in-process Matlab array In to representation
746% determined by Type.
747convert_mx(atom, X, Y) :- !, mlMX2ATOM(X,Y).
748convert_mx(bool, X, Y) :- !, mlMX2LOGICAL(X,Y).
749convert_mx(float, X, Y) :- !, mlMX2FLOAT(X,Y).
750convert_mx(int, X, Y) :- !, mlMX2FLOAT(X,Z), Y is truncate(Z).
751convert_mx(string, X, Y) :- !, mlMX2STRING(X,Y).
752convert_mx(term, X, Y) :- !, mlMX2ATOM(X,Z), term_to_atom(Y,Z).
753convert_mx(loc, X, mat(Y,W)) :- !, mlMX2ATOM(X,Z), term_to_atom(Y|W,Z).
754
755convert_mx(mat, X, Y) :- !, % !!! use first engine to save to its matbase
756 current_engine(I),
757 bt_call( db_save(I,mx(X),Y), db_drop(I,Y)).
758convert_mx(tmp, X, Y) :- !, % !!! use first engine to save to its matbase
759 current_engine(I),
760 bt_call( db_tmp(I,mx(X),Y), db_drop(I,Y)).
761
762convert_mx(list(float), X, Y) :- !, mlGETREALS(X,Y).
763
764convert_mx(cell(Type,Size), X, L) :- !,
765 mx_size_type(X,Size,cell),
766 prodlist(Size,1,Elems), % total number of elements
767 mapnats(conv_cref(Type,X),Elems,[],FL),
768 reverse(Size,RSize),
769 unflatten(RSize,FL,L).
770
771convert_mx(array(Type,Size), X, L) :- !,
772 mx_size_type(X,Size,MXType),
773 compatible(MXType,Type),
774 prodlist(Size,1,Elems), % total number of elements
775 mapnats(conv_aref(Type,X),Elems,[],FL),
776 reverse(Size,RSize),
777 unflatten(RSize,FL,L).
778
779compatible(double,float).
780compatible(double,int).
781compatible(double,bool).
782% compatible(logical,float).
783% compatible(logical,int).
784compatible(logical,bool).
785
786% !! Need to worry about non gc mx atoms
787conv_aref(bool, X,I,Y) :- !, mlGETLOGICAL(X,I,Y).
788conv_aref(float, X,I,Y) :- !, mlGETFLOAT(X,I,Y).
789conv_aref(int, X,I,Y) :- !, mlGETFLOAT(X,I,W), Y is truncate(W).
790
791conv_cref(mx,Z,I,Y) :- !, mlGETCELL(Z,I,Y). % !! non gc mx
792conv_cref(Ty,Z,I,Y) :- !, conv_cref(mx,Z,I,X), convert_mx(Ty,X,Y).
793
794%convert(W, field(Z,N,I)) :- convert(mx(X),Z), mlGETFIELD(X,I,N,Y), convert_mx(W,Y).
795%convert(W, field(Z,N)) :- convert(mx(X),Z), mlGETFIELD(X,1,N,Y), convert_mx(W,Y).
796
797% Utilities used by convert/2
798
799mapnats(P,N,L1,L3) :- succ(M,N), !, call(P,N,PN), mapnats(P,M,[PN|L1],L3).
800mapnats(_,0,L,L) :- !.
801
802prodlist([],P,P).
803prodlist([X1|XX],P1,P3) :- P2 is P1*X1, prodlist(XX,P2,P3).
804
805concat(0,_,[]) --> !, [].
806concat(N,L,[X1|XX]) --> { succ(M,N), length(X1,L) }, X1, concat(M,L,XX).
807
808% convert a flat list into a nested-list array representation
809% using given size specification
810unflatten([N],Y,Y) :- !, length(Y,N).
811unflatten([N|NX],Y,X) :-
812 length(Y,M),
813 L is M/N, integer(L), L>=1,
814 phrase(concat(N,L,Z),Y),
815 maplist(unflatten(NX),Z,X).
816
817% thin wrappers
818mx_size_type(X,Sz,Type) :- mlMXINFO(X,Sz,Type).
819mx_sub2ind(X,Subs,Ind) :- mlSUB2IND(X,Subs,Ind).
820
821
822% these create memory managed arrays, which are not suitable
823% for putting into a cell array
824
825% roughly, mx_create :: type -> mxarray.
826mx_create([Size],mx(X)) :- mlCREATENUMERIC(Size,Z), mlNEWREFGC(Z,X).
827mx_create({Size},mx(X)) :- mlCREATECELL(Size,Z), mlNEWREFGC(Z,X).
828mx_string(string(Y),mx(X)) :- mlCREATESTRING(Y,Z), mlNEWREFGC(Z,X).
829
830% MX as MUTABLE variables
831mx_put(aref(mx(X),I),float(Y)) :- mlPUTFLOAT(X,I,Y).
832mx_put(cref(mx(X),I),mx(Y)) :- mlPUTCELL(X,I,Y). % !! ensure that Y is non gc
833mx_put(mx(X),list(float,Y)) :- mlPUTFLOATS(X,1,Y).
834
835%% wsvar(+X:ws_blob(A), -Nm:atom, -Id:ml_eng) is semidet.
836% True if X is a workspace variable in Matlab session Id.
837% Unifies Nm with the name of the Matlab variable.
838wsvar(A,Name,Engine) :- mlWSNAME(A,Name,Engine).
839
840/* __________________________________________________________________________________
841 * Dealing with the Matbase
842 *
843 * The Matbase is a file system tree which contains lots of
844 * MAT files which have been created by using the dbsave
845 * Matlab function.
846 */
847
848
849%% loc(Dir,File)// is det.
850% DCG rule for matbase locator strings. Dir must be an atom slash-separated
851% list of atoms representing a path relative to the matbase root (see Matlab
852% function dbroot). File must be an atom. Outputs a single-quoted locator
853% string acceptable to Matlab db functions.
854loc(X,Y) --> "'", wr(X),"|",atm(Y), "'".
855
856
857% saving and dropping matbase files
858db_save(I,Z,Y) :- ml_eval(I,dbsave(Z),[loc],[Y]).
859db_tmp(I,Z,Y) :- ml_eval(I,dbtmp(Z),[loc],[Y]).
860db_drop(I,mat(A,B)) :- ml_exec(I,dbdrop(\loc(A,B))).
861
862db_save_all(I,Z,L,Size) :- ml_eval(I,dbcellmap(@dbsave,Z),[cell(loc,Size)],[L]).
863db_tmp_all(I,Z,L,Size) :- ml_eval(I,dbcellmap(@dbtmp,Z),[cell(loc,Size)],[L]).
864db_drop_all(I,L,Size) :-
865 length(Size,Dims),
866 ml_exec(I,hide(foreach(@dbdrop,arr(Dims,L,X\\{loc(X)})))).
867
868
869%% dropmat(+Id:ml_id, +Mat:ml_loc) is det.
870% Deleting MAT file from matbase.
871dropmat(Eng,mat(A,B)) :- db_drop(Eng,mat(A,B)).
872
873%% exportmat(+Id:ml_id, +Mat:ml_loc, +Dir:atom) is det.
874% Export specified MAT file from matbase to given directory.
875exportmat(Eng,mat(A,B),Dir) :- ml_exec(Eng,copyfile(dbpath(\loc(A,B)),\q(wr(Dir)))).
876
877%% matbase_mat(+Id:ml_eng,-X:ml_loc) is nondet.
878% Listing mat files actually in matbase at given root directory.
879matbase_mat(Id,mat(SubDir/File,x)) :-
880 ml_eval(Id,[dbroot,q(/)],[atom],[DBRoot]), % NB with trailing slash
881
882 atom_concat(DBRoot,'*/d*',DirPattern),
883 expand_file_name(DirPattern,Dirs),
884 member(FullDir,Dirs),
885 atom_concat( DBRoot,SubDirAtom,FullDir),
886 term_to_atom(SubDir,SubDirAtom),
887 atom_concat(FullDir,'/m*.mat',FilePattern),
888 expand_file_name(FilePattern,Files),
889 member(FullFile,Files),
890 file_base_name(FullFile,FN),
891 atom_concat(File,'.mat',FN).
892
893
894%% persist_item(+X:ml_expr(A),-Y:ml_expr(A)) is det.
895% Convert Matlab expression to persistent form not dependent on
896% current Matlab workspace or MX arrays in Prolog memory space.
897% Large values like arrays and structures are saved in the matbase
898% replaced with matbase locators. Scalar values are converted to
899% literal numeric values. Character strings are converted to Prolog atoms.
900% Cell arrays wrapped in the wsseq/1 functor are converted to literal
901% form.
902%
903% NB. any side effects are undone on backtracking -- in particular, any
904% files created in the matbase are deleted.
905persist_item($T,$T) :- !.
906persist_item(mat(A,B),mat(A,B)) :- !.
907
908persist_item(ws(A),B) :- !,
909 mlWSNAME(A,_,Eng),
910 ml_eval(Eng,typecode(ws(A)),[int,bool,bool],[Numel,IsNum,IsChar]),
911 ( Numel=1, IsNum=1
912 -> convert_ws(float,A,B)
913 ; IsChar=1
914 -> convert_ws(atom,A,AA), B= `AA
915 ; convert_ws(mat,A,B)
916 ).
917
918
923persist_item(wsseq(A),cell(B)) :-
924 mlWSNAME(A,_,Eng),
925 ml_test(Eng,iscell(ws(A))),
926 ml_eval(Eng,wsseq(A),[cell(mat,_)],[B]).
927
928persist_item(mx(X),B) :-
929 mx_size_type(X,Size,Type),
930 ( Size=[1], Type=double
931 -> convert_mx(float,X,B)
932 ; Type=char
933 -> convert_mx(atom,X,AA), B= `AA
934 ; convert_mx(mat,X,B)
935 ).
936
937persist_item(A,A) :- atomic(A).
938
939
940/* -----------------------------------------------------------------------
941 * From here on, we have straight Matlab utilities
942 * rather than basic infrastructure.
943 */
944
945
946% for dealing with option lists
947
948%% mhelp(+Name:atom) is det.
949% Lookup Matlab help on the given name. Equivalent to executing help(`X).
951mhelp(X) :- current_engine(Id), ansi_format([fg(blue)],'~@',[plml:ml_exec(Id,help(q(X)))]).
969compileoptions(Opts,Prefs) :-
970 rec_optslist(Opts,OptsList),
971 Prefs=..[prefs|OptsList].
972
973rec_optslist([],[]).
974rec_optslist([H|T],L) :-
975 ( % mutually exclusive types for H
976 optionset(H,Opts1) -> rec_optslist(Opts1,Opts)
977 ; H=Name:Value -> Opts=[`Name,Value]
978 ; is_list(H) -> rec_optslist(H,Opts)
979 ; /* assume struct */ Opts=[H]
980 ),
981 rec_optslist(T,TT),
982 append(Opts,TT,L).
983
984rtimes(X,Y,Z) :-
985 ( var(X) -> X is Z/Y
986 ; var(Y) -> Y is Z/X
987 ; Z is X*Y).
988
989
990% Execute several plots as subplots. The layout can be
991% vertical, horizontal, or explicity given as Rows*Columns.
992
993
994% mplot is a private procedure used by multiplot
995mplot(subplot(H,W),N,Plot,Ax) :- ?? (subplot(H,W,N); Plot), Ax===gca.
996mplot(figure,N,Plot,Ax) :- ?? (figure(N); Plot), Ax===gca.
997
998%% multiplot(+Type:ml_plot, +Cmds:list(ml_expr(_))) is det.
999%% multiplot(+Type:ml_plot, +Cmds:list(ml_expr(_)), -Axes:list(ml_val(handle))) is det.
1000%
1001% Executes plotting commands in Cmds in multiple figures or axes as determined
1002% by Type. Valid types are:
1003% * figs(Range)
1004% Executes each plot in a separate figure, Range must be P..Q where P
1005% and Q are figure numbers.
1006% * vertical
1007% Executes each plot in a subplot;
1008% subplots are arranged vertically top to bottom in the current figure.
1009% * horizontal
1010% Executes each plot in a subplot;
1011% subplots are arranged horizontally left to right in the current figure.
1012% * [Type, link(Axis)]
1013% As for multplot type Type, but link X or Y axis scales as determined by Axis,
1014% which can be `x, `y, or `xy.
1018multiplot(Type,Plots) :- multiplot(Type,Plots,_).
1019
1020multiplot([Layout|Opts],Plots,Axes) :- !,
1021 multiplot(Layout,Plots,Axes),
1022 member(link(A),Opts) ->
1023 ?? (linkaxes(Axes,`off); hide(linkaxes(Axes,`A)))
1024 ; true.
1025
1026multiplot(figs(P..Q),Plots,Axes) :- !,
1027 length(Plots,N),
1028 between(1,inf,P), Q is P+N-1,
1029 numlist(P,Q,PlotNums),
1030 maplist(mplot(figure),PlotNums,Plots,Axes).
1031
1032multiplot(Layout,Plots,Axes) :-
1033 length(Plots,N),
1034 member(Layout:H*W,[vertical:N*1, horizontal:1*N, H*W:H*W]),
1035 rtimes(H,W,N), 1036 numlist(1,N,PlotNums),
1037 maplist(mplot(subplot(H,W)),PlotNums,Plots,Axes).
1046
1047prolog:message(no_lang(Lang)) --> ['Environment variable LANG not set -- using ~w'-[Lang]].
1048prolog:message(plml_unknown_engine(Id)) --> ['Matlab engine (~w) does not exist'-[Id]].
1049prolog:message(ml_illegal_expression(Expr)) --> ['Illegal Matlab expression: ~w'-[Expr]].
1050prolog:message(ml_syntax_error) --> ['Unknown Matlab syntax error'].
1051prolog:message(ml_interrupted) --> ['Matlab computation was interrupted'].
1052prolog:message(error(mleng_error(Function,Arg),_)) -->
1053 ['Matlab engine API function ~w failed on "~s"',[Function,Arg]].
1054prolog:message(error(plml_error(Got,Expected),_)) -->
1055 ['plml protocol error: got ~q, expected ~q'-[Got,Expected]].
1056prolog:message(error(ml_error(Msg,Cmd),_)) -->
1057 {shorten(Cmd, ShortCmd)},
1058 ['Matlab error:'-[], nl, '>> ~w'-[Msg], nl],
1059 ['while executing "~s"'-[ShortCmd]].
1060
1061shorten(S1, S2) :-
1062 length(S1, L1),
1063 ( L1 =< 1000 -> S2=S1
1064 ; length(Pre,300), append(Pre,Rest,S1),
1065 length(Suff,300), append(_,Suff,Rest),
1066 append(" ... ", Suff, Inter),
1067 append(Pre, Inter, S2)
1068 ).
1074pl2tex(A=B) --> !, pl2tex(A), "=", pl2tex(B).
1075pl2tex(A+B) --> !, pl2tex(A), "+", pl2tex(B).
1076pl2tex(A-B) --> !, pl2tex(A), "-", pl2tex(B).
1077pl2tex(A*B) --> !, pl2tex(A), "*", pl2tex(B).
1078pl2tex(A.*B) --> !, pl2tex(A), "*", pl2tex(B).
1079pl2tex(A/B) --> !, pl2tex(A), "/", pl2tex(B).
1080pl2tex(A./B) --> !, pl2tex(A), "/", pl2tex(B).
1081pl2tex(A\B) --> !, pl2tex(A), "\\", pl2tex(B).
1082pl2tex(A.\B) --> !, pl2tex(A), "\\", pl2tex(B).
1083pl2tex(A^B) --> !, pl2tex(A), "^", brace(pl2tex(B)).
1084pl2tex(A.^B) --> !, pl2tex(A), "^", brace(pl2tex(B)).
1085pl2tex((A,B))--> !, pl2tex(A), ", ", pl2tex(B).
1086pl2tex(A;B)--> !, pl2tex(A), "; ", pl2tex(B).
1087pl2tex(A:B)--> !, pl2tex(A), ": ", pl2tex(B).
1088pl2tex({A}) --> !, "\\{", pl2tex(A), "\\}".
1089pl2tex([]) --> !, "[]".
1090pl2tex([X|XS]) --> !, "[", seqmap_with_sep(", ",pl2tex,[X|XS]), "]".
1091
1092pl2tex(A\\B) --> !, "\\lambda ", pl2tex(A), ".", pl2tex(B).
1093pl2tex(@A) --> !, "@", pl2tex(A).
1094pl2tex(abs(A)) --> !, "|", pl2tex(A), "|".
1095pl2tex(A) --> {atomic(A)}, escape_with(0'\\,0'_,at(A)).
1096pl2tex(A) -->
1097 {compound(A), A=..[H|T] },
1098 pl2tex(H), paren(seqmap_with_sep(", ",pl2tex,T)).
1099
1100hostname(H) :-
1101 ( getenv('HOSTNAME',H) -> true
1102 ; read_line_from_pipe(hostname,H)
1103 )
Prolog-Matlab interface
Types
ml_eng - Any atom identifying a Matlab engine.
ml_stmt - A Matlab statement
Matlab expression syntax
The Matlab expression syntax adopted by this module allows Prolog terms to represent or denote Matlab expressions. Let T be the domain of recognised Prolog terms (corresponding to the type ml_expr), and M be the domain of Matlab expressions written in Matlab syntax. Then V : T->M is the valuation function which maps Prolog term X to Matlab expression V[X]. These are some of the constructs it recognises:
Constructs valid only in top level statements, not subexpressions:
Things that look and work like Matlab syntax (more or less):
Things that do not look like Matlab syntax but provide standard Matlab features:
Referencing different value representations.
Tricky bits.
Things to bypass default formatting
All other Prolog atoms are written using write/1, while other Prolog terms are assumed to be calls to Matlab functions named according to the head functor. Thus V[ <head>( <arg1>, <arg2>, ...) ] = <head>(V[<arg1>, V[<arg2>], ...).
There are some incompatibilities between Matlab syntax and Prolog syntax, that is, syntactic structures that Prolog cannot parse correctly:
save('x','Y')
" can be written as "save x Y" in Matlab, but in Prolog, you must use function call syntax with quoted arguments: save(`x,`'Y').ctranspose(x)
".cref(x,1,2)
".mat(I)
andtmp(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? */