Source code for cis_interface.drivers.MatlabModelDriver

#
# This should not be used directly by modelers
#
import time
from logging import debug, warn
from datetime import datetime
import os
import sys
import weakref
try:  # pragma: matlab
    import matlab.engine
    _matlab_installed = True
except ImportError:  # pragma: no matlab
    warn("Could not import matlab.engine. " +
         "Matlab support will be disabled.")
    _matlab_installed = False
from cis_interface.drivers.ModelDriver import ModelDriver
from cis_interface.backwards import sio


_top_dir = os.path.normpath(os.path.join(os.path.dirname(__file__), '../'))
_incl_interface = os.path.join(_top_dir, 'interface')
_incl_io = os.path.join(_top_dir, 'io')


[docs]def start_matlab(): r"""Start a Matlab shared engine session inside a detached screen session. Returns: str: Name of the screen session running matlab. """ if _matlab_installed: # pragma: matlab old_matlab = set(matlab.engine.find_matlab()) screen_session = str('matlab' + datetime.today().strftime("%Y%j%H%M%S") + '_%d' % len(old_matlab)) os.system(('screen ' + '-dmS %s ' % screen_session + '-c %s ' % os.path.join(os.path.dirname(__file__), 'matlab_screenrc') + 'matlab -nodisplay -nosplash -nodesktop -nojvm ' + '-r "matlab.engine.shareEngine"')) while len(set(matlab.engine.find_matlab()) - old_matlab) == 0: debug('Waiting for matlab engine to start') time.sleep(1) # Usually 3 seconds new_matlab = list(set(matlab.engine.find_matlab()) - old_matlab)[0] else: # pragma: no matlab warn("Matlab not installed. Matlab could not be started.") screen_session, new_matlab = None, None return screen_session, new_matlab
[docs]def stop_matlab(screen_session, matlab_engine, matlab_session): r"""Stop a Matlab shared engine session running inside a detached screen session. Args: screen_session (str): Name of the screen session that the shared Matlab session was started in. matlab_engine (MatlabEngine): Matlab engine that should be stopped. matlab_session (str): Name of Matlab session that the Matlab engine is connected to. """ if _matlab_installed: # pragma: matlab # Remove weakrefs to engine to prevent stopping engine more than once if matlab_engine is not None: # Remove weak references so engine not deleted on exit eng_ref = weakref.getweakrefs(matlab_engine) for x in eng_ref: if x in matlab.engine._engines: matlab.engine._engines.remove(x) # Either exit the engine or remove its reference if matlab_session in matlab.engine.find_matlab(): matlab_engine.exit() else: # pragma: no cover matlab_engine.__dict__.pop('_matlab') # Stop the screen session containing the Matlab shared session if screen_session is not None: if matlab_session in matlab.engine.find_matlab(): os.system(('screen -X -S %s quit') % screen_session) while matlab_session in matlab.engine.find_matlab(): debug("Waiting for matlab engine to exit") time.sleep(1)
# else: # pragma: no matlab # warn("Matlab not installed. Matlab could not be stopped.")
[docs]class MatlabModelDriver(ModelDriver): r"""Base class for running Matlab models. Args: name (str): Driver name. args (str or list): Argument(s) for running the model in matlab. Generally, this should be the full path to a Matlab script. **kwargs: Additional keyword arguments are passed to parent class's __init__ method. Attributes: started_matlab (bool): True if the driver had to start a new matlab engine. False otherwise. screen_session (str): Screen session that Matlab was started in. mlengine (object): Matlab engine used to run script. mlsession (str): Name of the Matlab session that was started. """ def __init__(self, name, args, **kwargs): super(MatlabModelDriver, self).__init__(name, args, **kwargs) self.started_matlab = False self.screen_session = None self.mlengine = None self.mlsession = None if _matlab_installed: # pragma: matlab # Connect to matlab, start if not running if len(matlab.engine.find_matlab()) == 0: self.debug(": starting a matlab shared engine") self.screen_session, self.mlsession = start_matlab() self.started_matlab = True else: self.mlsession = matlab.engine.find_matlab()[0] try: self.mlengine = matlab.engine.connect_matlab(self.mlsession) except matlab.engine.EngineError: self.debug(": starting a matlab shared engine") self.screen_session, self.mlsession = start_matlab() self.started_matlab = True try: self.mlengine = matlab.engine.connect_matlab(self.mlsession) except matlab.engine.EngineError: # pragma: debug self.exception("could not connect to matlab engine") return # Add things to Matlab environment fdir = os.path.dirname(os.path.abspath(self.args[0])) self.mlengine.addpath(_top_dir, nargout=0) self.mlengine.addpath(_incl_interface, nargout=0) self.mlengine.addpath(fdir, nargout=0) self.debug(": connected to matlab") else: # pragma: no matlab self.screen_session, self.mlsession = start_matlab() def __del__(self): self.terminate()
[docs] def cleanup(self): r"""Close the Matlab session and engine.""" try: stop_matlab(self.screen_session, self.mlengine, self.mlsession) except SystemError: # pragma: debug self.error('.terminate failed to exit matlab engine') raise self.screen_session = None self.mlsession = None self.started_matlab = False self.mlengine = None
[docs] def on_exit(self): r"""Cleanup Matlab session and engine on exit.""" self.cleanup() super(MatlabModelDriver, self).on_exit()
[docs] def terminate(self): r"""Terminate the driver, including the matlab engine.""" with self.lock: self.cleanup() super(MatlabModelDriver, self).terminate()
[docs] def run(self): r"""Run the matlab script in the matlab engine.""" if _matlab_installed: # pragma: matlab self.debug('.run %s from %s', self.args[0], os.getcwd()) out = sio.StringIO() # err = sio.StringIO() # Add environment variables for k, v in self.env.items(): with self.lock: if self.mlengine is None: return self.mlengine.setenv(k, v, nargout=0) # Construct command # Strip the .m off - silly matlab name = os.path.splitext(os.path.basename(self.args[0]))[0] command = "self.mlengine." + name + "(" if len(self.args) > 1: for a in self.args[1:]: if isinstance(a, str): command += "'%s', " % a else: command += "%s, " % str(a) # command = command + ", ".join(self.args[1:]) +", " command += "stdout=out, " # command += "stderr=err, " command += "nargout=0)" self.debug(": command: %s", command) # Run with self.lock: if self.mlengine is None: # pragma: debug return eval(command) # Get otuput line = out.getvalue() sys.stdout.write(line) sys.stdout.flush() self.debug(".done") else: # pragma: no matlab warn("Matlab not installed. Could not run model.")