Source code for openmdao.components.external_code

import sys
import os

import numpy.distutils
from numpy.distutils.exec_command import find_executable

from openmdao.core.system import AnalysisError
from openmdao.core.component import Component
from openmdao.util.options import OptionsDictionary
from openmdao.util.shell_proc import STDOUT, DEV_NULL, ShellProc

from six import iteritems, itervalues

class ExternalCode(Component):
[docs] """ Run an external code as a component Default stdin is the 'null' device, default stdout is the console, and default stderr is ``error.out``. Options ------- deriv_options['type'] : str('user') Derivative calculation type ('user', 'fd', 'cs') Default is 'user', where derivative is calculated from user-supplied derivatives. Set to 'fd' to finite difference this system. Set to 'cs' to perform the complex step if your components support it. deriv_options['form'] : str('forward') Finite difference mode. (forward, backward, central) deriv_options['step_size'] : float(1e-06) Default finite difference stepsize deriv_options['step_calc'] : str('absolute') Set to absolute, relative deriv_options['check_type'] : str('fd') Type of derivative check for check_partial_derivatives. Set to 'fd' to finite difference this system. Set to 'cs' to perform the complex step method if your components support it. deriv_options['check_form'] : str('forward') Finite difference mode: ("forward", "backward", "central") During check_partial_derivatives, the difference form that is used for the check. deriv_options['check_step_calc'] : str('absolute',) Set to 'absolute' or 'relative'. Default finite difference step calculation for the finite difference check in check_partial_derivatives. deriv_options['check_step_size'] : float(1e-06) Default finite difference stepsize for the finite difference check in check_partial_derivatives" deriv_options['linearize'] : bool(False) Set to True if you want linearize to be called even though you are using FD. options['command'] : list([]) Command to be executed. Command must be a list of command line args. options['env_vars'] : dict({}) Environment variables required by the command options['external_input_files'] : list([]) (optional) list of input file names to check the existence of before solve_nonlinear options['external_output_files'] : list([]) (optional) list of input file names to check the existence of after solve_nonlinear options['poll_delay'] : float(0.0) Delay between polling for command completion. A value of zero will use an internally computed default. options['timeout'] : float(0.0) Maximum time in seconds to wait for command completion. A value of zero implies an infinite wait. If the timeout interval is exceeded, an AnalysisError will be raised. options['fail_hard'] : bool(True) Behavior on error returned from code, either raise a 'hard' error (RuntimeError) if True or a 'soft' error (AnalysisError) if False. """ def __init__(self): super(ExternalCode, self).__init__() self.STDOUT = STDOUT self.DEV_NULL = DEV_NULL # Input options for this Component self.options = OptionsDictionary() self.options.add_option('command', [], desc='command to be executed') self.options.add_option('env_vars', {}, desc='Environment variables required by the command') self.options.add_option('poll_delay', 0.0, lower=0.0, desc='Delay between polling for command completion. A value of zero will use an internally computed default') self.options.add_option('timeout', 0.0, lower=0.0, desc='Maximum time to wait for command completion. A value of zero implies an infinite wait') self.options.add_option( 'external_input_files', [], desc='(optional) list of input file names to check the existence of before solve_nonlinear') self.options.add_option( 'external_output_files', [], desc='(optional) list of input file names to check the existence of after solve_nonlinear') self.options.add_option('fail_hard', True, desc="If True, external code errors raise a 'hard' exception (RuntimeError). Otherwise raise a 'soft' exception (AnalysisError).") # Outputs of the run of the component or items that will not work with the OptionsDictionary self.return_code = 0 # Return code from the command self.stdin = self.DEV_NULL self.stdout = None self.stderr = "error.out" def check_setup(self, out_stream=sys.stdout):
[docs] """Write a report to the given stream indicating any potential problems found with the current configuration of this ``Problem``. Args ---- out_stream : a file-like object, optional """ # check for the command cmd = [c for c in self.options['command'] if c.strip()] if not cmd: out_stream.write( "The command cannot be empty") else: program_to_execute = self.options['command'][0] command_full_path = find_executable( program_to_execute ) if not command_full_path: out_stream.write("The command to be executed, '%s', " "cannot be found" % program_to_execute) # Check for missing input files missing = self._check_for_files(self.options['external_input_files']) if missing: out_stream.write("The following input files are missing at setup " " time: %s" % missing) def solve_nonlinear(self, params, unknowns, resids):
[docs] """Runs the component """ self.return_code = -12345678 if not self.options['command']: raise ValueError('Empty command list') if self.options['fail_hard']: err_class = RuntimeError else: err_class = AnalysisError return_code = None try: missing = self._check_for_files(self.options['external_input_files']) if missing: raise err_class("The following input files are missing: %s" % sorted(missing)) return_code, error_msg = self._execute_local() if return_code is None: raise AnalysisError('Timed out after %s sec.' % self.options['timeout']) elif return_code: if isinstance(self.stderr, str): if os.path.exists(self.stderr): stderrfile = open(self.stderr, 'r') error_desc = stderrfile.read() stderrfile.close() err_fragment = "\nError Output:\n%s" % error_desc else: err_fragment = "\n[stderr %r missing]" % self.stderr else: err_fragment = error_msg raise err_class('return_code = %d%s' % (return_code, err_fragment)) missing = self._check_for_files(self.options['external_output_files']) if missing: raise err_class("The following output files are missing: %s" % sorted(missing)) finally: self.return_code = -999999 if return_code is None else return_code def _check_for_files(self, files):
""" Check that specified files exist. """ return [path for path in files if not os.path.exists(path)] def _execute_local(self): """ Run command. """ # check to make sure command exists if isinstance(self.options['command'], str): program_to_execute = self.options['command'] else: program_to_execute = self.options['command'][0] # suppress message from find_executable function, we'll handle it numpy.distutils.log.set_verbosity(-1) command_full_path = find_executable( program_to_execute ) if not command_full_path: raise ValueError("The command to be executed, '%s', cannot be found" % program_to_execute) command_for_shell_proc = self.options['command'] if sys.platform == 'win32': command_for_shell_proc = ['cmd.exe', '/c' ] + command_for_shell_proc self._process = \ ShellProc(command_for_shell_proc, self.stdin, self.stdout, self.stderr, self.options['env_vars']) try: return_code, error_msg = \ self._process.wait(self.options['poll_delay'], self.options['timeout']) finally: self._process.close_files() self._process = None return (return_code, error_msg)