diff --git a/README.md b/README.md index ae6a54c..75728d1 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ This should make the python-matlab-bridge import-able. ## Usage -To use the pymatbridge you need to connect your python interperter to a Matlab +To use the pymatbridge you need to connect your python interpreter to a Matlab session. This is done in the following manner: from pymatbridge import Matlab @@ -72,12 +72,12 @@ This creates a matlab session class instance, into which you will be able to inject code and variables, and query for results. By default, when you use `start`, this will open whatever gets called when you type `matlab` in your Terminal, but you can also specify the location of your Matlab -application when initialzing your matlab session class: +application when initializing your matlab session class: - mlab = Matlab(matlab='/Applications/MATLAB_R2011a.app/bin/matlab') + mlab = Matlab(executable='/Applications/MATLAB_R2011a.app/bin/matlab') You can then start the Matlab server, which will kick off your matlab session, -and create the connection between your Python interperter and this session: +and create the connection between your Python interpreter and this session: mlab.start() @@ -99,7 +99,7 @@ the `get_variable` method: mlab.get_variable('a') -You can run any MATLAB functions contained within a .m file of the +You can run any MATLAB functions contained within a .m file of the same name. For example, to call the function jk in jk.m: %% MATLAB @@ -123,6 +123,37 @@ You can shut down the MATLAB server by calling: Tip: you can execute MATLAB code at the beginning of each of your matlab sessions by adding code to the `~/startup.m` file. +### Octave support & caveats + +A `pymatbridge.Octave` class is provided with exactly the same interface +as `pymatbridge.Matlab`: + + from pymatbridge import Octave + octave = Octave() + +Rather than looking for `matlab` at the shell, this will look for `octave`. +As with `pymatbridge.Matlab`, you can override this by specifying the +`executable` keyword argument. + +There are a few caveats to note about Octave support: + +* `pymatbridge.Matlab` invokes MATLAB with command line options that suppress +the display of figures -- these are instead saved to image files, accessible +via `results['content']['figures']` in the results dict. No such mechanism +seems to be available for Octave, so when drawing figures you'll likely see +them briefly pop up and disappear. (If you know of a way around this, feel +free to send a pull request). +* The zmq messenger used to communicate with the Octave session is written in C +and needs to be compiled. For MATLAB various prebuilt binaries are provided +and added to MATLAB's runtime path, but as of this writing the same isn't +true for Octave. You'll need to compile the messenger (in +`messenger/src/messenger.c`) using an incantation like +`mkoctfile --mex -lzmq messenger.c` and place the resulting `messenger.mex` +file somewhere in Octave's runtime path. + +Finally, rather than `~/startup.m`, Octave looks for an `~/.octaverc` file for +commands to execute before every session. (This is a good place to manipulate +the runtime path, for example). ### Matlab magic: diff --git a/messenger/src/messenger.c b/messenger/src/messenger.c index beb9a03..f14cb3c 100644 --- a/messenger/src/messenger.c +++ b/messenger/src/messenger.c @@ -90,10 +90,10 @@ void mexFunction(int nlhs, mxArray *plhs[], if (!initialized) { if (!initialize(socket_addr)) { - p[0] = true; + p[0] = 1; mexPrintf("Socket created at: %s\n", socket_addr); } else { - p[0] = false; + p[0] = 0; mexErrMsgTxt("Socket creation failed."); } } else { @@ -137,9 +137,9 @@ void mexFunction(int nlhs, mxArray *plhs[], p = mxGetLogicals(plhs[0]); if (msglen == respond(msg_out, msglen)) { - p[0] = true; + p[0] = 1; } else { - p[0] = false; + p[0] = 0; mexErrMsgTxt("Failed to send message due to ZMQ error"); } diff --git a/pymatbridge/matlab/matlabserver.m b/pymatbridge/matlab/matlabserver.m index 55b223b..9e20c1e 100644 --- a/pymatbridge/matlab/matlabserver.m +++ b/pymatbridge/matlab/matlabserver.m @@ -3,12 +3,12 @@ function matlabserver(socket_address) % over the socket. I then enters the listen-respond mode until it gets an % "exit" command -json.startup +json_startup messenger('init', socket_address); while(1) msg_in = messenger('listen'); - req = json.load(msg_in); + req = json_load(msg_in); switch(req.cmd) case {'connect'} diff --git a/pymatbridge/matlab/util/json_v0.2.2/+json/java/README b/pymatbridge/matlab/util/json_v0.2.2/json/java/README similarity index 100% rename from pymatbridge/matlab/util/json_v0.2.2/+json/java/README rename to pymatbridge/matlab/util/json_v0.2.2/json/java/README diff --git a/pymatbridge/matlab/util/json_v0.2.2/+json/java/json.jar b/pymatbridge/matlab/util/json_v0.2.2/json/java/json.jar similarity index 100% rename from pymatbridge/matlab/util/json_v0.2.2/+json/java/json.jar rename to pymatbridge/matlab/util/json_v0.2.2/json/java/json.jar diff --git a/pymatbridge/matlab/util/json_v0.2.2/+json/dump.m b/pymatbridge/matlab/util/json_v0.2.2/json/json_dump.m similarity index 89% rename from pymatbridge/matlab/util/json_v0.2.2/+json/dump.m rename to pymatbridge/matlab/util/json_v0.2.2/json/json_dump.m index 00690a0..e582047 100644 --- a/pymatbridge/matlab/util/json_v0.2.2/+json/dump.m +++ b/pymatbridge/matlab/util/json_v0.2.2/json/json_dump.m @@ -1,4 +1,4 @@ -function str = dump(value, varargin) +function str = json_dump(value, varargin) %DUMP Encode matlab value into a JSON string. % % SYNOPSIS @@ -64,7 +64,7 @@ % % See also json.load json.write - json.startup('WarnOnAddpath', true); + json_startup('WarnOnAddpath', true); options = get_options_(varargin{:}); obj = dump_data_(value, options); if isempty(options.indent) @@ -95,11 +95,12 @@ function obj = dump_data_(value, options) %DUMP_DATA_ if ischar(value) && (isvector(value) || isempty(value)) - obj = java.lang.String(value); + obj = javaObject('java.lang.String', value); elseif isempty(value) && isnumeric(value) - obj = org.json.JSONObject.NULL; + json_object = javaObject('org.json.JSONObject'); + obj = json_object.NULL; elseif ~isscalar(value) - obj = org.json.JSONArray(); + obj = javaObject('org.json.JSONArray'); if ndims(value) > 2 split_value = num2cell(value, 1:ndims(value)-1); @@ -124,13 +125,13 @@ end end elseif iscell(value) - obj = org.json.JSONArray(); + obj = javaObject('org.json.JSONArray'); for i = 1:numel(value) obj.put(dump_data_(value{i}, options)); end elseif isnumeric(value) if isreal(value) - obj = java.lang.Double(value); + obj = javaObject('java.lang.Double', value); % Encode complex number as a struct else complex_struct = struct; @@ -139,9 +140,9 @@ obj = dump_data_(complex_struct, options); end elseif islogical(value) - obj = java.lang.Boolean(value); + obj = javaObject('java.lang.Boolean', value); elseif isstruct(value) - obj = org.json.JSONObject(); + obj = javaObject('org.json.JSONObject'); keys = fieldnames(value); for i = 1:length(keys) obj.put(keys{i},dump_data_(value.(keys{i}), options)); @@ -149,4 +150,4 @@ else error('json:typeError', 'Unsupported data type: %s', class(value)); end -end \ No newline at end of file +end diff --git a/pymatbridge/matlab/util/json_v0.2.2/+json/load.m b/pymatbridge/matlab/util/json_v0.2.2/json/json_load.m similarity index 91% rename from pymatbridge/matlab/util/json_v0.2.2/+json/load.m rename to pymatbridge/matlab/util/json_v0.2.2/json/json_load.m index 3829a79..39dcf6d 100644 --- a/pymatbridge/matlab/util/json_v0.2.2/+json/load.m +++ b/pymatbridge/matlab/util/json_v0.2.2/json/json_load.m @@ -1,4 +1,4 @@ -function value = load(str, varargin) +function value = json_load(str, varargin) %LOAD Load matlab value from a JSON string. % % SYNOPSIS @@ -57,7 +57,7 @@ % % See also json.dump json.read - json.startup('WarnOnAddpath', true); + json_startup('WarnOnAddpath', true); options = get_options_(varargin{:}); singleton = false; @@ -66,11 +66,11 @@ error('json:invalidString','Invalid JSON string'); end if str(1)=='{' - node = org.json.JSONObject(java.lang.String(str)); + node = javaObject('org.json.JSONObject', javaObject('java.lang.String', str)); else singleton = str(1) ~= '[' && str(end) ~= ']'; if singleton, str = ['[',str,']']; end - node = org.json.JSONArray(java.lang.String(str)); + node = javaObject('org.json.JSONArray', javaObject('java.lang.String', str)); end value = parse_data_(node, options); if singleton, value = value{:}; end @@ -119,7 +119,7 @@ warning('json:fieldNameConflict', ... 'Field %s renamed to %s', field, safe_field); end - value.(safe_field) = parse_data_(node.get(java.lang.String(key)), ... + value.(safe_field) = parse_data_(node.get(javaObject('java.lang.String', key)), ... options); end % Check if the struct just decoded represents a complex number @@ -127,7 +127,8 @@ complex_value = complex(value.real, value.imag); value = complex_value; end - elseif isa(node, 'org.json.JSONObject$Null') + % In MATLAB, nested classes end up with a $ in the name, in Octave it's a . + elseif isa(node, 'org.json.JSONObject$Null') || isa(node, 'org.json.JSONObject.Null') value = []; else error('json:typeError', 'Unknown data type: %s', class(node)); @@ -196,4 +197,4 @@ fields = fieldnames(value); vec = [vec, uint8([fields{:}])]; end -end \ No newline at end of file +end diff --git a/pymatbridge/matlab/util/json_v0.2.2/+json/read.m b/pymatbridge/matlab/util/json_v0.2.2/json/json_read.m similarity index 88% rename from pymatbridge/matlab/util/json_v0.2.2/+json/read.m rename to pymatbridge/matlab/util/json_v0.2.2/json/json_read.m index 411bc6e..213fa47 100644 --- a/pymatbridge/matlab/util/json_v0.2.2/+json/read.m +++ b/pymatbridge/matlab/util/json_v0.2.2/json/json_read.m @@ -1,4 +1,4 @@ -function value = read(filename, varargin) +function value = json_read(filename, varargin) %READ Load a matlab value from a JSON file. % % SYNOPSIS @@ -19,7 +19,7 @@ fid = 0; try fid = fopen(filename, 'r'); - value = json.load(fscanf(fid, '%c', inf)); + value = json_load(fscanf(fid, '%c', inf)); fclose(fid); catch e if fid ~= 0, fclose(fid); end diff --git a/pymatbridge/matlab/util/json_v0.2.2/+json/startup.m b/pymatbridge/matlab/util/json_v0.2.2/json/json_startup.m similarity index 97% rename from pymatbridge/matlab/util/json_v0.2.2/+json/startup.m rename to pymatbridge/matlab/util/json_v0.2.2/json/json_startup.m index f88edac..77c0853 100644 --- a/pymatbridge/matlab/util/json_v0.2.2/+json/startup.m +++ b/pymatbridge/matlab/util/json_v0.2.2/json/json_startup.m @@ -1,4 +1,4 @@ -function startup(varargin) +function json_startup(varargin) %STARTUP Initialize runtime environment. % % SYNOPSIS @@ -42,4 +42,4 @@ function startup(varargin) ]); end end -end \ No newline at end of file +end diff --git a/pymatbridge/matlab/util/json_v0.2.2/+json/write.m b/pymatbridge/matlab/util/json_v0.2.2/json/json_write.m similarity index 87% rename from pymatbridge/matlab/util/json_v0.2.2/+json/write.m rename to pymatbridge/matlab/util/json_v0.2.2/json/json_write.m index 707dd08..8d8862c 100644 --- a/pymatbridge/matlab/util/json_v0.2.2/+json/write.m +++ b/pymatbridge/matlab/util/json_v0.2.2/json/json_write.m @@ -1,4 +1,4 @@ -function write(value, filename, varargin) +function json_write(value, filename, varargin) %WRITE Write a matlab value into a JSON file. % % SYNOPSIS @@ -19,7 +19,7 @@ function write(value, filename, varargin) fid = 0; try fid = fopen(filename, 'w'); - fprintf(fid, '%s', json.dump(value, varargin{:})); + fprintf(fid, '%s', json_dump(value, varargin{:})); fclose(fid); catch e if fid ~= 0, fclose(fid); end diff --git a/pymatbridge/matlab/util/pymat_eval.m b/pymatbridge/matlab/util/pymat_eval.m index b071cb8..510d753 100644 --- a/pymatbridge/matlab/util/pymat_eval.m +++ b/pymatbridge/matlab/util/pymat_eval.m @@ -26,7 +26,7 @@ if ~code_check response.message = 'No code provided as POST parameter'; - json_response = json.dump(response); + json_response = json_dump(response); return; end @@ -71,6 +71,6 @@ response.content.code = code; -json_response = json.dump(response); +json_response = json_dump(response); end %function diff --git a/pymatbridge/matlab/util/pymat_feval.m b/pymatbridge/matlab/util/pymat_feval.m index 2fd3a48..137abcb 100644 --- a/pymatbridge/matlab/util/pymat_feval.m +++ b/pymatbridge/matlab/util/pymat_feval.m @@ -20,7 +20,7 @@ if ~func_path_check response.message = 'No function given as func_path POST parameter'; - json_response = json.dump(response); + json_response = json_dump(response); return end @@ -35,7 +35,7 @@ response.success = 'true'; response.message = 'Successfully completed request'; - json_response = json.dump(response); + json_response = json_dump(response); return diff --git a/pymatbridge/matlab/util/pymat_get_variable.m b/pymatbridge/matlab/util/pymat_get_variable.m index c37fa90..391a133 100644 --- a/pymatbridge/matlab/util/pymat_get_variable.m +++ b/pymatbridge/matlab/util/pymat_get_variable.m @@ -17,7 +17,7 @@ if ~varname_check response.message = 'No variable name provided as input argument'; - json_response = json.dump(response); + json_response = json_dump(response); return end @@ -26,7 +26,7 @@ response.var = evalin('base', varname); -json_response = json.dump(response); +json_response = json_dump(response); return end diff --git a/pymatbridge/matlab_magic.py b/pymatbridge/matlab_magic.py index 384bc13..b7d7f92 100644 --- a/pymatbridge/matlab_magic.py +++ b/pymatbridge/matlab_magic.py @@ -114,7 +114,7 @@ def eval(self, line): """ Parse and evaluate a single line of matlab """ - run_dict = self.Matlab.run_code(line, maxtime=self.Matlab.maxtime) + run_dict = self.Matlab.run_code(line) if run_dict['success'] == 'false': raise MatlabInterperterError(line, run_dict['content']['stdout']) @@ -127,8 +127,7 @@ def set_matlab_var(self, name, value): Set up a variable in Matlab workspace """ run_dict = self.Matlab.run_func("pymat_set_variable.m", - {'name':name, 'value':value}, - maxtime=self.Matlab.maxtime) + {'name':name, 'value':value}) if run_dict['success'] == 'false': raise MatlabInterperterError(line, run_dict['content']['stdout']) diff --git a/pymatbridge/pymatbridge.py b/pymatbridge/pymatbridge.py index 8e6c95c..8cd7fb4 100644 --- a/pymatbridge/pymatbridge.py +++ b/pymatbridge/pymatbridge.py @@ -12,9 +12,7 @@ import os, time import zmq import subprocess -import platform import sys - import json # JSON encoder extension to handle complex numbers @@ -31,32 +29,17 @@ def as_complex(dct): return complex(dct['real'], dct['imag']) return dct - MATLAB_FOLDER = '%s/matlab' % os.path.realpath(os.path.dirname(__file__)) -# Start a Matlab server and bind it to a ZMQ socket(TCP/IPC) -def _run_matlab_server(matlab_bin, matlab_socket_addr, matlab_log, matlab_id, matlab_startup_options): - command = matlab_bin - command += ' %s ' % matlab_startup_options - command += ' -r "' - command += "addpath(genpath(" - command += "'%s'" % MATLAB_FOLDER - command += ')), matlabserver(\'%s\'),exit"' % matlab_socket_addr - - if matlab_log: - command += ' -logfile ./pymatbridge/logs/matlablog_%s.txt > ./pymatbridge/logs/bashlog_%s.txt' % (matlab_id, matlab_id) - - subprocess.Popen(command, shell = True, stdin=subprocess.PIPE) - - return True - -class Matlab(object): +class _Session(object): """ - A class for communicating with a matlab session + A class for communicating with a MATLAB session. It provides the behavior + common across different MATLAB implementations. You shouldn't instantiate + this directly; rather, use the Matlab or Octave subclasses. """ - def __init__(self, matlab='matlab', socket_addr=None, + def __init__(self, executable, socket_addr=None, id='python-matlab-bridge', log=False, maxtime=60, platform=None, startup_options=None): """ @@ -65,63 +48,75 @@ def __init__(self, matlab='matlab', socket_addr=None, Parameters ---------- - matlab : str - A string that woul start matlab at the terminal. Per default, this - is set to 'matlab', so that you can alias in your bash setup + executable : str + A string that would start the session at the terminal. socket_addr : str A string that represents a valid ZMQ socket address, such as "ipc:///tmp/pymatbridge", "tcp://127.0.0.1:55555", etc. id : str - An identifier for this instance of the pymatbridge + An identifier for this instance of the pymatbridge. log : bool Whether to save a log file in some known location. maxtime : float - The maximal time to wait for a response from matlab (optional, + The maximal time to wait for a response from the session (optional, Default is 10 sec) platform : string The OS of the machine on which this is running. Per default this will be taken from sys.platform. + startup_options : string + Command line options to include in the executable's invocation. + Optional; sensible defaults are used if this is not provided. """ - # Setup internal state variables self.started = False self.running = False - self.matlab = matlab + self.executable = executable self.socket_addr = socket_addr - self.id = id self.log = log self.maxtime = maxtime + self.platform = platform if platform is not None else sys.platform + self.startup_options = startup_options - if platform is None: - self.platform = sys.platform - else: - self.platform = platform - - if self.socket_addr is None: # use the default + if socket_addr is None: self.socket_addr = "tcp://127.0.0.1:55555" if self.platform == "win32" else "ipc:///tmp/pymatbridge" - if startup_options: - self.startup_options = startup_options - elif self.platform == 'win32': - self.startup_options = ' -automation -noFigureWindows' - else: - self.startup_options = ' -nodesktop -nodisplay' + if self.log: + startup_options += ' > ./pymatbridge/logs/bashlog_%s.txt' % self.id self.context = None self.socket = None + def _program_name(self): + raise NotImplemented + + def _preamble_code(self): + return ["addpath(genpath('%s'))" % MATLAB_FOLDER] + + def _execute_flag(self): + raise NotImplemented + + def _run_server(self): + code = self._preamble_code() + code.extend([ + "matlabserver('%s')" % self.socket_addr, + 'exit' + ]) + command = '%s %s %s "%s"' % (self.executable, self.startup_options, + self._execute_flag(), ','.join(code)) + subprocess.Popen(command, shell=True, stdin=subprocess.PIPE) + # Start server/client session and make the connection def start(self): # Start the MATLAB server in a new process - print "Starting MATLAB on ZMQ socket %s" % (self.socket_addr) + print "Starting %s on ZMQ socket %s" % (self._program_name(), self.socket_addr) print "Send 'exit' command to kill the server" - _run_matlab_server(self.matlab, self.socket_addr, self.log, self.id, self.startup_options) + self._run_server() # Start the client self.context = zmq.Context() @@ -131,23 +126,24 @@ def start(self): self.started = True # Test if connection is established - if (self.is_connected()): - print "MATLAB started and connected!" + if self.is_connected(): + print "%s started and connected!" % self._program_name() return True else: - print "MATLAB failed to start" + print "%s failed to start" % self._program_name() return False - - # Stop the Matlab server - def stop(self): - req = json.dumps(dict(cmd="exit"), cls=ComplexEncoder) + def _response(self, **kwargs): + req = json.dumps(kwargs, cls=ComplexEncoder) self.socket.send(req) resp = self.socket.recv_string() + return resp + # Stop the Matlab server + def stop(self): # Matlab should respond with "exit" if successful - if resp == "exit": - print "MATLAB closed" + if self._response(cmd='exit') == "exit": + print "%s closed" % self._program_name() self.started = False return True @@ -162,69 +158,145 @@ def is_connected(self): self.socket.send(req) start_time = time.time() - while(True): + while True: try: resp = self.socket.recv_string(flags=zmq.NOBLOCK) - if resp == "connected": - return True - else: - return False + return resp == "connected" except zmq.ZMQError: sys.stdout.write('.') time.sleep(1) - if (time.time() - start_time > self.maxtime) : - print "Matlab session timed out after %d seconds" % (self.maxtime) + if time.time() - start_time > self.maxtime: + print "%s session timed out after %d seconds" % (self._program_name(), self.maxtime) return False - def is_function_processor_working(self): - result = self.run_func('%s/test_functions/test_sum.m' % MATLAB_FOLDER, {'echo': 'Matlab: Function processor is working!'}) - if result['success'] == 'true': - return True - else: - return False - + result = self.run_func('%s/test_functions/test_sum.m' % MATLAB_FOLDER, + {'echo': '%s: Function processor is working!' % self._program_name()}) + return result['success'] == 'true' - # Run a function in Matlab and return the result - def run_func(self, func_path, func_args=None, maxtime=None): + def _json_response(self, **kwargs): if self.running: time.sleep(0.05) + return json.loads(self._response(**kwargs), object_hook=as_complex) - req = dict(cmd="run_function") - req['func_path'] = func_path - req['func_args'] = func_args + # Run a function in Matlab and return the result + def run_func(self, func_path, func_args=None): + return self._json_response(cmd='run_function', + func_path=func_path, + func_args=func_args) - req = json.dumps(req, cls=ComplexEncoder) - self.socket.send(req) - resp = self.socket.recv_string() - resp = json.loads(resp, object_hook=as_complex) + # Run some code in Matlab command line provide by a string + def run_code(self, code): + return self._json_response(cmd='run_code', code=code) - return resp + def get_variable(self, varname): + return self._json_response(cmd='get_var', varname=varname)['var'] - # Run some code in Matlab command line provide by a string - def run_code(self, code, maxtime=None): - if self.running: - time.sleep(0.05) - req = dict(cmd="run_code") - req['code'] = code - req = json.dumps(req, cls=ComplexEncoder) - self.socket.send(req) - resp = self.socket.recv_string() - resp = json.loads(resp, object_hook=as_complex) +class Matlab(_Session): + def __init__(self, executable='matlab', socket_addr=None, + id='python-matlab-bridge', log=False, maxtime=60, + platform=None, startup_options=None): + """ + Initialize this thing. - return resp + Parameters + ---------- - def get_variable(self, varname, maxtime=None): - if self.running: - time.sleep(0.05) + executable : str + A string that would start Matlab at the terminal. Per default, this + is set to 'matlab', so that you can alias in your bash setup - req = dict(cmd="get_var") - req['varname'] = varname - req = json.dumps(req, cls=ComplexEncoder) - self.socket.send(req) - resp = self.socket.recv_string() - resp = json.loads(resp, object_hook=as_complex) + socket_addr : str + A string that represents a valid ZMQ socket address, such as + "ipc:///tmp/pymatbridge", "tcp://127.0.0.1:55555", etc. + + id : str + An identifier for this instance of the pymatbridge. + + log : bool + Whether to save a log file in some known location. - return resp['var'] + maxtime : float + The maximal time to wait for a response from matlab (optional, + Default is 10 sec) + + platform : string + The OS of the machine on which this is running. Per default this + will be taken from sys.platform. + startup_options : string + Command line options to pass to MATLAB. Optional; sensible defaults + are used if this is not provided. + """ + if platform is None: + platform = sys.platform + if startup_options is None: + if platform == 'win32': + startup_options = ' -automation -noFigureWindows' + else: + startup_options = ' -nodesktop -nodisplay' + if log: + startup_options += ' -logfile ./pymatbridge/logs/matlablog_%s.txt' % id + super(Matlab, self).__init__(executable, socket_addr, id, log, maxtime, + platform, startup_options) + + def _program_name(self): + return 'MATLAB' + + def _execute_flag(self): + return '-r' + + +class Octave(_Session): + def __init__(self, executable='octave', socket_addr=None, + id='python-matlab-bridge', log=False, maxtime=60, + platform=None, startup_options=None): + """ + Initialize this thing. + + Parameters + ---------- + + executable : str + A string that would start Octave at the terminal. Per default, this + is set to 'octave', so that you can alias in your bash setup + + socket_addr : str + A string that represents a valid ZMQ socket address, such as + "ipc:///tmp/pymatbridge", "tcp://127.0.0.1:55555", etc. + + id : str + An identifier for this instance of the pymatbridge. + + log : bool + Whether to save a log file in some known location. + + maxtime : float + The maximal time to wait for a response from octave (optional, + Default is 10 sec) + + platform : string + The OS of the machine on which this is running. Per default this + will be taken from sys.platform. + + startup_options : string + Command line options to pass to Octave. Optional; sensible defaults + are used if this is not provided. + """ + if startup_options is None: + startup_options = '--silent --no-gui' + super(Octave, self).__init__(executable, socket_addr, id, log, maxtime, + platform, startup_options) + + def _program_name(self): + return 'Octave' + + def _preamble_code(self): + code = super(Octave, self)._preamble_code() + if self.log: + code.append("diary('./pymatbridge/logs/octavelog_%s.txt')" % self.id) + return code + + def _execute_flag(self): + return '--eval' diff --git a/pymatbridge/tests/test_run_code.py b/pymatbridge/tests/test_run_code.py index 45b2ae8..2693ce1 100644 --- a/pymatbridge/tests/test_run_code.py +++ b/pymatbridge/tests/test_run_code.py @@ -23,7 +23,10 @@ def test_disp(self): npt.assert_equal(result1, "Hello world\n") npt.assert_equal(result2, " \n") - npt.assert_equal(result3, "") + if tu.on_octave(): + npt.assert_equal(result3, '\n') + else: + npt.assert_equal(result3, "") # Make some assignments and run basic operations def test_basic_operation(self): @@ -34,13 +37,20 @@ def test_basic_operation(self): result_product = self.mlab.run_code("a * b")['content']['stdout'] result_division = self.mlab.run_code("c = a / b")['content']['stdout'] - - npt.assert_equal(result_assignment_a, unicode("\na =\n\n 21.2345\n\n")) - npt.assert_equal(result_assignment_b, unicode("\nb =\n\n 347.7450\n\n")) - npt.assert_equal(result_sum, unicode("\nans =\n\n 368.9795\n\n")) - npt.assert_equal(result_diff, unicode("\nans =\n\n -326.5105\n\n")) - npt.assert_equal(result_product, unicode("\nans =\n\n 7.3842e+03\n\n")) - npt.assert_equal(result_division, unicode("\nc =\n\n 0.0611\n\n")) + if tu.on_octave(): + npt.assert_equal(result_assignment_a, unicode("a = 21.235\n")) + npt.assert_equal(result_assignment_b, unicode("b = 347.75\n")) + npt.assert_equal(result_sum, unicode("ans = 368.98\n")) + npt.assert_equal(result_diff, unicode("ans = -326.51\n")) + npt.assert_equal(result_product, unicode("ans = 7384.2\n")) + npt.assert_equal(result_division, unicode("c = 0.061063\n")) + else: + npt.assert_equal(result_assignment_a, unicode("\na =\n\n 21.2345\n\n")) + npt.assert_equal(result_assignment_b, unicode("\nb =\n\n 347.7450\n\n")) + npt.assert_equal(result_sum, unicode("\nans =\n\n 368.9795\n\n")) + npt.assert_equal(result_diff, unicode("\nans =\n\n -326.5105\n\n")) + npt.assert_equal(result_product, unicode("\nans =\n\n 7.3842e+03\n\n")) + npt.assert_equal(result_division, unicode("\nc =\n\n 0.0611\n\n")) # Put in some undefined code def test_undefined_code(self): @@ -48,4 +58,7 @@ def test_undefined_code(self): message = self.mlab.run_code("this_is_nonsense")['content']['stdout'] npt.assert_equal(success, "false") - npt.assert_equal(message, "Undefined function or variable 'this_is_nonsense'.") + if tu.on_octave(): + npt.assert_equal(message, "'this_is_nonsense' undefined near line 1 column 1") + else: + npt.assert_equal(message, "Undefined function or variable 'this_is_nonsense'.") diff --git a/pymatbridge/tests/test_utils.py b/pymatbridge/tests/test_utils.py index 0d8131e..ade76f3 100644 --- a/pymatbridge/tests/test_utils.py +++ b/pymatbridge/tests/test_utils.py @@ -1,8 +1,13 @@ +import os + import pymatbridge as pymat import numpy.testing as npt +def on_octave(): + return bool(os.environ.get('USE_OCTAVE', False)) + def connect_to_matlab(): - mlab = pymat.Matlab() + mlab = pymat.Octave() if on_octave() else pymat.Matlab() mlab.start() npt.assert_(mlab.is_connected(), msg = "connect_to_matlab(): Connection failed")