1 #-------------------------------------------------------------------------------
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 #-------------------------------------------------------------------------------
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
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/>.
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 *
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.
51 appname : The name of the application
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.
63 bindir : The directory that could not be created.
64 msg : A message describing the error condition in more detail.
67 Exception.__init
__(self
, bindir
, msg
)
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.
80 entry : The entry that could not be found.
81 filename: Name of the config file.
84 Exception.__init
__(self
, entry
, filename
)
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.
97 case : The case that isn't decomposed
100 Exception.__init
__(self
, case
)
103 """Return string representation of the error."""
104 return ('ERROR: Cannot run case %s in parallel, '+
105 'because it is not decomposed.'%self
.args
)
109 """A class to run FreeFOAM applications."""
111 def __init__(self
, search_path
=_f
.search_path
):
112 """Initializes the Runner object.
116 path : The search path for FreeFOAM applications.
120 self
._search
_path
= search_path
121 # holds the subprocess.Popen object of a running job
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
131 app
= locate_app(appname
, self
._search
_path
)
133 raise ApplicationNotFoundError(appname
)
135 # assemble command line
138 command
.append('-case')
139 command
.append(_op
.normpath(case
))
141 command
.append('-parallel')
142 if type(args
) == str:
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."""
153 if 'OMPI_MCA_universe' in _os
.environ
:
155 sys
.stderr
.write('Warning: Probably running in OpenMPI, '+
156 'but -parallel not specified\n')
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.
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
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
179 args : Iterable or string of additional arguments and options to be
180 passed to the application.
184 result : The exit code of the application if 'background' is False,
189 ApplicationNotFoundError : If the application 'appname' cannot be found.
194 self
._check
_parallel
(parallel
)
195 command
= self
._create
_command
(appname
, case
=case
,
196 parallel
=parallel
, args
=args
)
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
205 self
.returncode
= None
206 self
._process
= subprocess
.Popen(command
, stdin
=stdin
, stdout
=stdout
,
208 # should we run in the background?
211 # nope, so we wait, delete the _process object and return the exit code
213 self
.returncode
= self
._process
.returncode
215 return self
.returncode
218 """Check if child process has terminated.
219 Set and return returncode attribute. If the process terminated, the
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.
229 self
.returncode
= self
._process
.poll()
231 self
.returncode
= None
232 return self
.returncode
234 def getProcess(self
):
235 """Access the Popen object."""
238 def command_str(self
, appname
, case
=None, parallel
=False, args
=[]):
239 """Return the command line to run a FreeFOAM job.
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
247 args : Iterable or string of additional arguments and options to be
248 passed to the application.
252 result : A string with the command line.
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)"""
272 # if parallel desired, check whether decomposed:
273 is_decomposed
=_op
.isdir(_op
.join(case
, 'processor0'))
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
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)
284 template = m.group(1)
286 raise EntryNotFoundError('parRunTemplate', controlDict)
290 'APPLICATION': ' '.join(
291 Runner._create_command(self, appname, case, False, [])),
292 'ARGS': ' '.join(args),
294 elif parallel and not is_decomposed:
295 raise NotDecomposedError(case)
297 cmd = Runner._create_command(self, appname, case, False, args)
300 def _check_parallel(self, parallel):
304 # ------------------------- vim: set sw=3 sts=3 et: --------------- end-of-file