Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# (c) Stefan Countryman 2016-2018
3"""
4FileHandlers that make calls to MATLAB in order to generate their data. You
5will need to specify the directory containing MATLAB functions to call with the
6environmental variable ``MATLABDIR``.
7"""
9import os
10from abc import abstractproperty
11import subprocess
12from glob import glob
13import logging
14import distutils.spawn
15from llama.classes import optional_env_var
16from llama.utils import LOGDIR
17from llama.filehandler import (
18 GenerationError,
19 FileHandler,
20)
21LOGGER = logging.getLogger(__name__)
23MATLAB_LOGFILE = os.path.join(LOGDIR, 'llama.matlab.log')
24MATLABDIR = optional_env_var(
25 ['MATLABDIR'],
26 f"""
27 Specify path to directory containing MATLAB commands using
28 environmental variable ``MATLABDIR`` in order to generate MATLAB
29 ``FileHandler`` instances. No ``MATLABDIR`` specified; defaulting to
30 CWD: {os.getcwd()}.
31 """,
32)[0]
36def matlabpath():
37 """Get a path to a ``matlab`` executable (if it exists). If something called
38 ``matlab`` is in your ``$PATH``, this will be used. Otherwise, if you have
39 specified ``$MATLAB_EXECUTABLE_PATH`` as an environmental variable, that path
40 will be used. If both of these checks fail, the most recent version of
41 ``MATLAB`` matching the pattern
42 ``/Applications/MATLAB_R20*.app/bin/matlab`` will be used (only applicable
43 on MacOS)."""
44 matlab = distutils.spawn.find_executable('matlab')
45 if matlab is not None:
46 return matlab
47 if 'MATLAB_EXECUTABLE_PATH' in os.environ:
48 return os.environ['MATLAB_EXECUTABLE_PATH']
49 matches = sorted(glob("/Applications/MATLAB_R20*.app/bin/matlab"))
50 if matches:
51 return matches[-1]
52 raise OSError("Could not find a MATLAB executable.")
55def matlab_eval(matcmd, cli_flags=None):
56 """Run a command in a headless instance of MATLAB. By default, changes to
57 the Neutrinos directory of the repo directory, which is where all LLAMA
58 MATLAB code is currently stored. NOTE that your
59 command can have multiple statements, but it CANNOT have newlines; it
60 must have semicolons separating valid statements (See example below).
62 You can also provide a list of command line flags like "-nojvm" to modify
63 MATLAB's behavior. *This implementation will be removed if we switch to
64 MATLAB Engine, since MATLAB is not called through the command line
65 interface when using the MATLAB Engine interface.*
67 Examples
68 --------
69 Print "foo" and "bar" on separate lines, checking first if MATLAB is
70 installed:
71 >>> try:
72 ... matpath = matlabpath()
73 ... matlab_eval("disp('foo');disp('bar')")
74 ... except OSError:
75 ... # put your cleanup code here
76 ... pass
77 """
78 if cli_flags is None:
79 cli_flags = list()
80 # write output to the MATLAB logfile. TODO implement true logging.
81 with open(MATLAB_LOGFILE, 'a') as logfile:
82 # retrieve the path to the MATLAB executable from the environment
83 if '\n' in matcmd:
84 raise ValueError('MATLAB command cannot contain a newline.')
85 cmd = [
86 matlabpath(), '-nodesktop', '-nosplash', '-noFigureWindows', '-r',
87 "cd {}; ".format(MATLABDIR) +
88 "try {}; ".format(matcmd) +
89 "catch err; " +
90 " fprintf(2, getReport(err)); " +
91 " exit(64); " +
92 "end; " +
93 "disp('done'); exit"]
94 cmd += cli_flags
95 proc = subprocess.Popen(cmd, stdout=logfile, stderr=logfile)
96 proc.communicate()
97 if proc.returncode != 0:
98 raise Exception(("Something went wrong. See debug output in: " +
99 MATLAB_LOGFILE))
102class MATLABCallingFileHandler(FileHandler):
103 """
104 A class that is generated by calling a MATLAB function. The generator
105 just calls the matlab function specified by the ``matlab_command`` property
106 using MATLAB command-line options specified by ``matlab_options``.
107 """
109 def _generate(self): # pylint: disable=W0221
110 LOGGER.debug('Running MATLAB command:')
111 LOGGER.debug(self.matlab_options)
112 try:
113 matlab_eval(self.matlab_command, self.matlab_options)
114 except Exception:
115 import traceback
116 LOGGER.error('MATLAB failed to generate %s', self)
117 LOGGER.error(traceback.format_exc())
118 raise GenerationError('matlab_exec failed while generating %s',
119 self)
121 # command-line options to pass to MATLAB
122 matlab_options = []
124 @abstractproperty
125 def matlab_command(self):
126 """Return the command that MATLAB will eval in order to generate this
127 file."""