FIX: New a2x (since 8.6.7) options in doxyToX.py
[freefoam.git] / data / python / FreeFOAM / run.py
blob4184065276af8ccd3bcf4c69dcc4230d1ea07e1e
1 #-------------------------------------------------------------------------------
2 # ______ _ ____ __ __
3 # | ____| _| |_ / __ \ /\ | \/ |
4 # | |__ _ __ ___ ___ / \| | | | / \ | \ / |
5 # | __| '__/ _ \/ _ ( (| |) ) | | |/ /\ \ | |\/| |
6 # | | | | | __/ __/\_ _/| |__| / ____ \| | | |
7 # |_| |_| \___|\___| |_| \____/_/ \_\_| |_|
9 # FreeFOAM: The Cross-Platform CFD Toolkit
11 # Copyright (C) 2008-2012 Michael Wild <themiwi@users.sf.net>
12 # Gerber van der Graaf <gerber_graaf@users.sf.net>
13 #-------------------------------------------------------------------------------
14 # License
15 # This file is part of FreeFOAM.
17 # FreeFOAM is free software: you can redistribute it and/or modify it
18 # under the terms of the GNU General Public License as published by the
19 # Free Software Foundation, either version 3 of the License, or (at your
20 # option) any later version.
22 # FreeFOAM is distributed in the hope that it will be useful, but WITHOUT
23 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
24 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
25 # for more details.
27 # You should have received a copy of the GNU General Public License
28 # along with FreeFOAM. If not, see <http://www.gnu.org/licenses/>.
30 # Description
31 # Useful Python functionality to run FreeFOAM cases
33 #-------------------------------------------------------------------------------
35 """Classes and functions useful for running FreeFOAM applications."""
37 # want to be future proof
38 from FreeFOAM.compat import *
40 import os as _os
41 import os.path as _op
42 import FreeFOAM as _f
44 class ApplicationNotFoundError(Exception):
45 """Thrown by run_job if the requested application could not be found."""
46 def __init__(self, appname):
47 """Initializes the exception object.
49 Parameters
50 ----------
51 appname : The name of the application
53 """
54 Exception.__init__(self, "ERROR: Application not found: %s"%appname)
56 class BinDirCreationError(Exception):
57 """Thrown if the creation of the binary directory by compile_application failed."""
58 def __init__(self, bindir, msg):
59 """Initialize the exception object.
61 Parameters
62 ----------
63 bindir : The directory that could not be created.
64 msg : A message describing the error condition in more detail.
66 """
67 Exception.__init__(self, bindir, msg)
69 def __str__():
70 """Return string representation of the error."""
71 return 'ERROR: Failed to create "%s": %s'%self.args
73 class EntryNotFoundError(Exception):
74 """Thrown if an entry couldn't be found in a config file."""
75 def __init__(self, entry, filename):
76 """Initialize the exception object.
78 Parameters
79 ----------
80 entry : The entry that could not be found.
81 filename: Name of the config file.
83 """
84 Exception.__init__(self, entry, filename)
86 def __str__(self):
87 """Return string representation of the error."""
88 return ('ERROR: Failed to find entry %s in %s.'%self.args)
90 class NotDecomposedError(Exception):
91 """Thrown if case should be run in parallel but is not decomposed."""
92 def __init__(self, case):
93 """Initialize the exception object.
95 Parameters
96 ----------
97 case : The case that isn't decomposed
99 """
100 Exception.__init__(self, case)
102 def __str__(self):
103 """Return string representation of the error."""
104 return ('ERROR: Cannot run case %s in parallel, '+
105 'because it is not decomposed.'%self.args)
108 class Runner:
109 """A class to run FreeFOAM applications."""
111 def __init__(self, search_path=_f.search_path):
112 """Initializes the Runner object.
114 Parameters
115 ----------
116 path : The search path for FreeFOAM applications.
119 # set up path
120 self._search_path = search_path
121 # holds the subprocess.Popen object of a running job
122 self._process = None
123 # the exit code of the last process that ran. None if the process is still
124 # running or if no process ran at all so far.
125 self.returncode = None
127 def _create_command(self, appname, case, parallel, args):
128 """Assembles the command line (as a list, suitable for subprocess)"""
129 from FreeFOAM.path import locate_app
130 # find executable
131 app = locate_app(appname, self._search_path)
132 if not app:
133 raise ApplicationNotFoundError(appname)
135 # assemble command line
136 command = [app]
137 if case:
138 command.append('-case')
139 command.append(_op.normpath(case))
140 if parallel:
141 command.append('-parallel')
142 if type(args) == str:
143 args = [args]
144 if len(args):
145 command.extend(args)
146 return command
148 def _check_parallel(self, parallel):
149 """Checks a few (currently OpenMPI) characteristics to
150 detect whether this is parallel run and issues a warning if the
151 `parallel` flag does not match."""
152 import sys
153 if 'OMPI_MCA_universe' in _os.environ:
154 if not parallel:
155 sys.stderr.write('Warning: Probably running in OpenMPI, '+
156 'but -parallel not specified\n')
157 else:
158 if parallel:
159 sys.stderr.write('Warning: Probably not running in OpenMPI, '+
160 'but -parallel specified\n')
163 def run(self, appname, background=False, case=None, parallel=False,
164 stdin=None, stdout=None, stderr=None, args=[]):
165 """Run a FreeFOAM job.
167 Parameters
168 ----------
169 appname : Name or full/relative path to the application to run.
170 background : Run the application in the background.
171 case : Path to the case to run. Defaults to the current directory.
172 parallel : If True, the '-parallel' flag will be passed to the
173 application.
174 stdin, stdout and stderr:
175 The program's standard input, output and error streams,
176 respectively. Valid values are a file descriptor, a
177 file object or None (in which case the system streams
178 will be used).
179 args : Iterable or string of additional arguments and options to be
180 passed to the application.
182 Returns
183 -------
184 result : The exit code of the application if 'background' is False,
185 None otherwise.
187 Throws
188 ------
189 ApplicationNotFoundError : If the application 'appname' cannot be found.
192 import subprocess
193 import sys
194 self._check_parallel(parallel)
195 command = self._create_command(appname, case=case,
196 parallel=parallel, args=args)
197 if stdin == None:
198 stdin = sys.stdin
199 if stdout == None:
200 stdout = sys.stdout
201 # make sure that the global controlDict is found
202 if 'FREEFOAM_CONFIG_DIR' not in _os.environ:
203 _os.environ['FREEFOAM_CONFIG_DIR'] = _f.config_dir
204 # run the command
205 self.returncode = None
206 self._process = subprocess.Popen(command, stdin=stdin, stdout=stdout,
207 stderr=stderr)
208 # should we run in the background?
209 if background:
210 return None
211 # nope, so we wait, delete the _process object and return the exit code
212 self._process.wait()
213 self.returncode = self._process.returncode
214 self._process = None
215 return self.returncode
217 def poll(self):
218 """Check if child process has terminated.
219 Set and return returncode attribute. If the process terminated, the
220 object is freed.
222 Returns
223 -------
224 returncode : The exit code of the process or None if the process is still
225 running or no process has ever been run by this object.
228 if self._process:
229 self.returncode = self._process.poll()
230 else:
231 self.returncode = None
232 return self.returncode
234 def getProcess(self):
235 """Access the Popen object."""
236 return self._process
238 def command_str(self, appname, case=None, parallel=False, args=[]):
239 """Return the command line to run a FreeFOAM job.
241 Parameters
242 ----------
243 appname : Name or full/relative path to the application to run.
244 case : Path to the case to run. Defaults to the current directory.
245 parallel : If True, the '-parallel' flag will be passed to the
246 application.
247 args : Iterable or string of additional arguments and options to be
248 passed to the application.
250 Returns
251 -------
252 result : A string with the command line.
254 Throws
255 ------
256 ApplicationNotFoundError : If the application 'appname' cannot be found.
259 command = self._create_command(appname, case=case,
260 parallel=parallel, args=args)
261 return ' '.join(command)
263 class ParallelRunner(Runner):
264 """Class to run FreeFOAM cases in parallel."""
266 # override the _create_command function
267 def _create_command(self, appname, case, parallel, args):
268 """Assembles the command line (as a list, suitable for subprocess)"""
269 import re
270 import glob
271 import sys
272 # if parallel desired, check whether decomposed:
273 is_decomposed =_op.isdir(_op.join(case, 'processor0'))
274 cmd = None
275 if parallel and is_decomposed:
276 # count number of processors
277 nproc = len(glob.glob(_op.join(case,'processor*')))
278 # create command from the template in global controlDict
279 template = None
280 controlDict = _op.join(_f.config_dir, 'controlDict')
281 for l in open(controlDict, 'rt'):
282 m = re.match(r'\s*parRunTemplate\s+["\'](.*)["\']\s*;\s*$', l)
283 if m:
284 template = m.group(1)
285 if not template:
286 raise EntryNotFoundError('parRunTemplate', controlDict)
287 cmd = (template%{
288 'NPROCS': nproc,
289 'PAROPTS': '',
290 'APPLICATION': ' '.join(
291 Runner._create_command(self, appname, case, False, [])),
292 'ARGS': ' '.join(args),
293 }).split()
294 elif parallel and not is_decomposed:
295 raise NotDecomposedError(case)
296 if not cmd:
297 cmd = Runner._create_command(self, appname, case, False, args)
298 return cmd
300 def _check_parallel(self, parallel):
301 """Do nothing."""
302 pass
304 # ------------------------- vim: set sw=3 sts=3 et: --------------- end-of-file