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,
   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
   36	,	term_mlstring/3   % (+Id, +Expr, -String)  ~Prolog term to Matlab string
   37	,	term_texatom/2		% (+Expr, -Atom)         ~Prolog term to TeX expression
   38	,	wsvar/3      		% (+WSBlob, -Name, -Id)
   39
   40	% MATBASE
   41	,	persist_item/2 	% (+Expr,-Expr)     ~ convert volatile subterms to persistent form
   42	,	matbase_mat/2     % (+Dir, -Loc)      ~ Find matbase MAT files
   43	,	dropmat/2         % (+Id, +Loc)       ~ remove MAT file from matbase
   44	,	exportmat/3 		% (+Id, +Loc, +Dir) ~ export MAT file from matbase
   45
   46
   47	% Utilities
   48	,	compileoptions/2
   49	,	multiplot/2
   50	,  mhelp/1
   51
   52	,	op(650,fy,'`')	 	% quoting things
   53	,	op(160,xf,'``')		% postfix transpose operator
   54	,	op(100,fy,@)		% function handles
   55
   56	% note slightly reduced precedence of array operators -
   57	% hope this doesn't break everything...
   58	,	op(210,xfy,.^) 	% array exponentiation
   59	,	op(410,yfx,.*) 	% array times
   60	,	op(410,yfx,./) 	% array division
   61	,	op(410,xfy,.\) 	% reverse array division
   62	,	op(400,xfy,\) 		% reverse matrix division
   63	,	op(700,xfx,===) 	% variable binding/assignment in matlab query
   64	,	op(700,xfx,:==) 	% variable binding/assignment in matlab query
   65	,	op(951,fx,??) 		% evaluate term as matlab
   66	,	op(951,fx,???) 	% evaluate term as matlab boolean
   67	,	op(100,yfx,#)     % field indexing (note left-associativity)
   68	,	op(750,fy,\\)		% thunk abdstraction
   69	,  op(750,xfy,\\)    % lambda abdstraction
   70
   71	% exported after being imported from ops
   72	,	op(1100,xfx,::)	% type specification (esp for arrays)
   73	,	op(550,xfx,..)		% range of integers
   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).

Prolog-Matlab interface

Types

ml_eng - Any atom identifying a Matlab engine.

ml_stmt - A Matlab statement

X;Y     :: ml_stmt :-  X::ml_stmt, Y::ml_stmt.
X,Y     :: ml_stmt :-  X::ml_stmt, Y::ml_stmt.
X=Y     :: ml_stmt :-  X::ml_lval, Y::ml_expr.
hide(X) :: ml_stmt :-  X::ml_stmt.
ml_expr(A)       % A Matlab expression, possibly with multiple return values
ml_loc ---> mat(atom,atom).  % Matbase locator

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:

X;Y             % |--> V[X]; V[Y]  (sequential evaluation hiding first result)
X,Y             % |--> V[X], V[Y]  (sequential evaluation displaying first result)
X=Y             % |--> V[X]=V[Y] (assignment, X must denote a valid left-value)
hide(X)         % |--> V[X]; (execute X but hide return value)
if(X,Y)         % |--> if V[X], V[Y], end
if(X,Y,Z)       % |--> if V[X], V[Y], else V[Z], end

Things that look and work like Matlab syntax (more or less):

+X              % |--> uplus(V[X])
-X              % |--> uminus(V[X])
X+Y             % |--> plus(V[X],V[Y])
X-Y             % |--> minus(V[X],V[Y])
X^Y             % |--> mpower(V[X],V[Y])
X*Y             % |--> mtimes(V[X],V[Y])
X/Y             % |--> mrdivide(V[X],V[Y])
X\Y             % |--> mldivide(V[X],V[Y])
X.^Y            % |--> power(V[X],V[Y])
X.*Y            % |--> times(V[X],V[Y])
X./Y            % |--> rdivide(V[X],V[Y])
X.\Y            % |--> ldivide(V[X],V[Y])
X:Y:Z           % |--> colon(V[X],V[Y],V[Z])
X:Z             % |--> colon(V[X],V[Z])
X>Z             % |--> gt(V[X],V[Y])
X>=Z            % |--> ge(V[X],V[Y])
X<Z             % |--> lt(V[X],V[Y])
X=<Z            % |--> le(V[X],V[Y])
X==Z            % |--> eq(V[X],V[Y])
[X1,X2,...]     % |--> [ V[X1], V[X2], ... ]
[X1;X2;...]     % |--> [ V[X1]; V[X2]; ... ]
{X1,X2,...}     % |--> { V[X1], V[X2], ... }
{X1;X2;...}     % |--> { V[X1]; V[X2]; ... }
@X              % |--> @V[X] (function handle)

Things that do not look like Matlab syntax but provide standard Matlab features:

'Infinity'      % |--> inf (positive infinity)
'Nan'           % |--> nan (not a number)
X``             % |--> ctranpose(V[X]) (conjugate transpose, V[X]')
X#Y             % |--> getfield(V[X],V[q(Y)])
X\\Y            % |--> @(V[X])V[Y] (same as lambda(X,Y))
\\Y             % |--> @()V[Y] (same as thunk(Y))
lambda(X,Y)     % |--> @(V[X])V[Y] (anonymous function with arguments X)
thunk(Y)        % |--> @()V[Y] (anonymous function with no arguments)
vector(X)       % |--> horzcat(V[X1],V[X2], ...)
atvector(X)     % as vector but assumes elements of X are assumed all atomic
cell(X)         % construct 1xN cell array from elements of X
`X              % same as q(X)
q(X)            % wrap V[X] in single quotes (escaping internal quotes)
tq(X)           % wrap TeX expression in single quotes (escape internal quotes)

Referencing different value representations.

mat(X,Y)           % denotes a value in the Matbase using a dbload expression
mx(X:mx_blob)      % denotes an MX Matlab array in SWI memory
ws(X:ws_blob)      % denotes a variable in a Matlab workspace
wsseq(X:ws_blob)   % workspace variable containing list as cell array.

Tricky bits.

apply(X,AX)        % X must denote a function or array, applied to list of arguments AX.
cref(X,Y)          % cell dereference, |--> V[X]{ V[Y1], V[Y2], ... }
arr(Lists)         % multidimensional array from nested lists.
arr(Lists,Dims)    % multidimensional array from nested lists.

Things to bypass default formatting

noeval(_)          % triggers a failure when processed
atom(X)            % write atom X as write/1
term(X)            % write term X as write/1
\(P)               % escape and call phrase P directly to generate Matlab string
$(X)               % calls pl2ml_hook/2, denotes V[Y] where plml_hook(X,Y).
'$VAR'(N)          % gets formatted as p_N where N is assumed to be atomic.

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:

To be done
- 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? */

  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,===). % variable binding/assignment in matlab query
  242:- op(951,fx,??).  % evaluate term as matlab
  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))))).
 build(+Spec, +Cmd1:string, -Cmd2:string) is det
This predicate is responsible for building the command to run Matlab.
  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))).
 ml_close(+Id:ml_eng) is det
Close Matlab session associated with Id.
  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)).
 ml_exec(+Id:ml_eng, +Expr:ml_expr) is det
Execute Matlab expression without returning any values.
  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).
 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.
  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).
 ml_test(+Id:ml_eng, +X:ml_expr(bool)) is semidet
Succeeds if X evaluates to true in Matlab session Id.
  444ml_test(Id,X)   :- ml_eval(Id,X,[bool],[1]).
  445
  446ml_ws_name(X,Y,Z) :- mlWSNAME(X,Y,Z).
 ===(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.
  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	).
 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.
  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).
 ??(X:ml_expr(_)) is det
Execute Matlab expression X as with ml_exec/2, without returning any values. Uses current (last opened) engine
  486?? X    :- current_engine(Id), ml_exec(Id,X).
 ???(X:ml_expr(bool)) is semidet
Evaluate Matlab boolean expression X as with ml_test/2. Uses current (last opened) engine
  491??? Q   :- current_engine(Id), ml_test(Id,Q).
  492
  493
  494/*
  495 * DCG for term to matlab conversion
  496 *  the big problem with Matlab syntax is that you cannot always replace
  497 *  a name representing a value with an expression that reduces to that
  498 *  value. Eg
  499 *     X=magic(5), X(3,4)
  500 *  is ok, but
  501 *     (magic(5))(3,4)
  502 *  is not. Similarly x=@sin, x(0.5)  but not (@sin)(0.5)
  503 *  This is really infuriating.
  504 */
  505
  506
  507% top level statement rules
  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).
 ml_expr(+Id:ml_eng, +X:ml_expr(A))// is nondet
Convert Matlab expression as a Prolog term to string representation.
  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). % this could be dangerous
  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}. % causes evaluation to 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
  919% !! TODO -
  920%     deal with collections - we can either save the aggregate
  921%     OR save the elements individually and get a prolog list of the
  922%     locators.
  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).
  950%  If using a colour terminal, output is written in blue.
  951mhelp(X) :- current_engine(Id), ansi_format([fg(blue)],'~@',[plml:ml_exec(Id,help(q(X)))]).
 compileoptions(+Opts:list(ml_options), -Prefs:ml_expr(options)) is det
Convert list of option specifiers into a Matlab expression representing options (ie a struct). Each specifier can be a Name:Value pair, a name to be looked up in the optionset/2 predicate, a nested list of ml_options compileoptions :: list (optionset | atom:value | struct) -> struct. NB. option types are as follows:
X :: ml_options :- optionset(X,_).
X :: ml_options :- X :: ml_option(_).
X :: ml_options :- X :: list(ml_options).
X :: ml_options :- X :: ml_expr(struct(_)).

ml_option(A) ---> atom:ml_expr(A).
  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.
 1015%
 1016%  Three argument form returns a list containing the Matlab handles to axes objects,
 1017%  one for each plot.
 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), % bind any remaining variables
 1036	numlist(1,N,PlotNums),
 1037	maplist(mplot(subplot(H,W)),PlotNums,Plots,Axes).
 optionset(+Key:term, -Opts:list(ml_options)) is semidet
Extensible predicate for mapping arbitrary terms to a list of options to be processed by compileoptions/2.
 1045%user:portray(Z) :- mlWSNAME(Z,N,ID), format('<~w:~w>',[ID,N]).
 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   ).
 pl2tex(+Exp:tex_expr)// is det
DCG for texifying expressions (useful for matlab text)
 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   )