1 """!Object structure for describing MPI programs.
3 Do not load this module directly. It is meant to be loaded only by
4 the produtil.run module.
6 This module handles execution of MPI programs, and execution of
7 groups of non-MPI programs through an MPI interface (which requires
8 all sorts of tricks). This module is also the interface to the
9 various produtil.mpi_impl.* modules that generate the shell command to
10 run MPI programs. This module is built on top of the produtil.prog
11 module and uses it to run the MPI-launching program for your local
12 cluster (mpiexec, mpirun, poe, etc.)
14 In addition, this module contains code to simplify adding new MPI
15 implementations to the produtil.mpi_impl subpackage. High-level code,
16 such as the HWRF scripts, use the produtil.run module to generate
17 object trees of MPIRanksBase objects. The produtil.mpi_impl
18 subpackages then implement an mpirunner function that turns those into
19 a produtil.prog.Runner to be directly executed. The MPIRanksBase
20 object, and its subclasses, implement a few utilites to automate that
23 * to_arglist --- converts the MPI ranks to an mpi launcher command
24 as a produtil.prog.Runner, or to an array of strings
27 * nranks --- calculates the number of requested MPI ranks
29 * expand_iter --- iterates over groups of identical MPI ranks
31 * check_serial --- tells whether this program is running MPI programs,
32 or running serial programs as if they were MPI
33 (or both, which most MPI implementations don't support)
35 For MPI implementations that require a command file, see the
36 produtil.mpi_impl.mpi_impl_base CMDFGen class to have the
37 produtil.prog module automatically write the command file before
38 executing the program. The produtil.mpi_impl.mpirun_lsf shows an
39 example of how to use it.
41 See the produtil.run module for full documentation."""
51 """!Base class of syntax errors in MPI program specifications"""
53 """!Raised when something that cannot be expressed as a pure MPI
54 rank is given as a pure MPI rank."""
56 """!Raised when an MPI program was expected but something else was
59 """!Raised when a serial program was expected, but something else
62 """!Raised when the validation scripts were expecting string
63 arguments or string executable names, but something else was
69 """!This is the abstract superclass of all classes that represent
70 one or more MPI ranks, including MPI ranks that are actually
71 serial programs. Subclasses of MPIRanksBase allow an MPI program
72 to be represented as a tree of MPIRanksBase objects, in such a way
73 that they can be easily converted to a produtil.prog.Runner object
74 for execution. The actual conversion to a Runner is done in the
75 produtil.mpi_impl package (see produtil/mpi_impl/__init__.py)"""
77 def to_arglist(self,to_shell=False,expand=False,shell_validate=None,
78 pre=[],before=[],between=[],after=[],post=[],extra={}):
79 """!This is the underlying implementation of most of the
80 mpi_impl modules, and hence make_runner as well. It converts
81 this group of MPI ranks into a set of arguments suitable for
82 sending to a Runner object or for writing to a command file.
83 This is done by iterating over either all ranks (if
84 expand=True) or groups of repeated ranks (if expand=False),
85 converting their arguments to a list. It prepends an
86 executable, and can insert other arguments in specified
87 locations (given in the pre, before, between, after, and post
88 arguments). It can also use the to_shell argument to convert
89 programs to POSIX sh commands, and it performs simple string
90 interpolation via the "extra" hash.
92 If to_shell=False then the executable and arguments are
93 inserted directly to the output list. Otherwise (when
94 to_shell=True) the to_shell subroutine is called on the
95 MPIRank object to produce a single argument that contains a
96 shell command. That single argument is then used in place of
97 the executable and arguments. Note that may raise
98 NotValidPosixSh (or a subclass thereof) if the command cannot
99 be expressed as a shell command. In addition, if
100 shell_validate is not None, then it is called on each
101 post-conversion shell argument, and the return value is used
104 You can specify additional argument lists to be inserted in
105 certain locations. Each argument in those lists will be
106 processed through the % operator, specifying "extra" as the
107 keyword list with two new keywords added: nworld is the number
108 of ranks in the MPI program, and "n" is the number in the
109 current group of repeated ranks if expand=False (n=1 if
110 expand=True). Those argument lists are: pre, before, between,
113 @param to_shell If True, convert executable and arguments to
114 a POSIX sh command instead of inserting them directly.
115 @param expand If True, groups of repeated ranks are expanded.
116 @param shell_validate A function to convert each argument to
117 some "shell-acceptable" version.
118 @param pre Inserted before everything else. This is where you
119 would put the "mpiexec" and any global settings.
120 @param before Inserted before each rank (if expand=True) or group
122 @param between Inserted between each rank (if expand=True) or group
124 @param after Inserted after each rank (if expand=True) or group (if
126 @param post Appended at the end of the list of arguments.
127 @param extra used for string expansion"""
129 kw[
'nworld']=self.
nranks()
130 for x
in pre:
yield x%kw
133 assert(isinstance(rank,MPIRanksBase))
134 assert(isinstance(count,int))
140 for x
in between:
yield x%kw
142 for x
in before:
yield x%kw
144 if shell_validate
is not None:
145 yield shell_validate(rank.to_shell())
147 yield rank.to_shell()
149 for arg
in rank.args():
yield arg
150 for x
in after:
yield x%kw
151 for x
in post:
yield x%kw
153 """!Returns a copy of this object where all child
154 produtil.prog.Runner objects have been replaced with
155 produtil.prog.ImmutableRunner objects."""
157 """!Returns a logger.Logger object for this MPIRanksBase or one
158 from its child MPIRanksBase objects (if it has any). If no
159 logger is found, None is returned."""
162 """!Returns a tuple (s,p) where s=True if there are serial
163 ranks in this part of the MPI program, and p=True if there are
164 parallel ranks. Note that it is possible that both could be
165 True, which is an error. It is also possible that neither are
166 True if there are zero ranks."""
169 """!Returns the number of ranks in this part of the MPI
173 """!Iterates over all MPIRank objects in this part of the MPI
177 """!Returns the number of groups of repeated MPI ranks in the
181 """!Iterates over all groups of repeating MPI ranks in the MPI
182 program returning tuples (r,c) containing a rank r and the
183 count (number) of that rank c.
184 @param threads If True, then a three-element tuple is
185 iterated, (r,c,t) where the third element is the number of
190 """!Returns the number of threads requested by this MPI rank,
191 or by each MPI rank in this group of MPI ranks. If different
192 ranks have different numbers of threads, returns the maximum
193 requested. Returns None if no threads are requested."""
195 for r,c,t
in self.groups(threads=
True):
196 if t
is not None and n
is None:
198 elif t
is not None and n
is not None and t>n:
202 """!Sets the number of threads requested by each MPI rank
203 within this group of MPI ranks."""
208 """!Removes the request for threads."""
211 threads=property(getthreads,setthreads,delthreads,
"""The number of threads per rank.""")
214 """!Returns True if this part of
215 the MPI program is the same as another part.
216 @param other the other object"""
217 return NotImplemented
219 """!Returns a new set of MPI ranks that consist of this group
220 of ranks repeated "factor" times.
221 @param factor how many times to duplicate"""
222 return NotImplemented
224 """!Returns a new set of MPI ranks that consist of this group
225 of ranks repeated "factor" times.
226 @param other how many times to duplicate"""
227 return NotImplemented
229 """!Returns a new set of MPI ranks that consist of this set of
230 ranks with the "other" set appended.
231 @param other the data to append"""
232 return NotImplemented
234 """!Returns a new set of MPI ranks that consist of the "other"
235 set of ranks with this set appended.
236 @param other the data to prepend"""
237 return NotImplemented
239 """!Determines if this set of MPI ranks can be represented by a
240 single serial executable with a single set of arguments run
241 without MPI. Returns false by default: this function can only
242 return true for MPISerial."""
245 """!Returns a POSIX sh command that will execute the serial
246 program, if possible, or raise a subclass of NotValidPosixSh
247 otherwise. Works only on single MPI ranks that are actually
248 MPI wrappers around a serial program (ie.: from mpiserial)."""
249 raise NotSerialProg(
'This is an MPI program, so it cannot be represented as a non-MPI POSIX sh command.')
251 """!This is a wrapper around ranks() and groups() which will
252 call self.groups() if expand=False. If expand=True, this will
253 call ranks() returning a tuple (rank,1) for each rank.
254 @param expand If True, expand groups of identical ranks into one
256 @param threads If True, then a third element will be in each
257 tuple: the number of requested threads per MPI rank."""
260 for rank
in self.
ranks():
261 yield (rank,1,self.threads)
263 for rank
in self.
ranks():
266 for rank,count,threads
in self.
groups(threads=
True):
267 yield (rank,count,threads)
269 for rank,count
in self.
groups(threads=
False):
272 """!Returns a string representation of this object intended for debugging."""
273 raise NotImplementedError(
'This class did not implement __repr__.')
278 """!Represents one MPI program duplicated across many ranks."""
280 """!MPIRanksSPMD constructor
281 @param mpirank the program to run
282 @param count how many times to run it"""
283 if not isinstance(mpirank,MPIRank):
288 """!Returns a new MPIRanksSPMD with an immutable version of
292 """!Returns "X*N" where X is the MPI program and N is the
296 """!Returns 1 or 0: 1 if there are ranks and 0 if there are none."""
302 """!Yields a tuple (X,N) where X is the mpi program and N is
303 the number of ranks."""
309 """!Returns a deep copy of self."""
312 """!Iterates over MPI ranks within self."""
314 for i
in xrange(self.
_count):
317 """!Returns the number of ranks this program requests."""
323 """!Multiply the number of requested ranks by some factor."""
324 if not isinstance(factor,int):
325 return NotImplemented
328 """!Multiply the number of requested ranks by some factor."""
329 if not isinstance(factor,int):
330 return NotImplemented
333 """!Add some new ranks to self. If they are not identical to
334 the MPI program presently requested, this returns a new
337 ocount=other.nranks()
338 for mpirank,count
in other.groups():
347 """!Checks to see if this program contains serial (non-MPI) or
349 @returns a tuple (serial,parallel) where serial is True if
350 there are serial components, and parallel is True if there are
351 parallel components. If there are no components, returns
354 return self._mpirank.check_serial()
358 """!Returns my MPI program's logger."""
359 return self._mpirank.get_logger()
364 """!Represents a group of MPI programs, each of which have some
365 number of ranks assigned."""
367 """!MPIRanksMPMD constructor
368 @param args an array of MPIRanksBase to execute."""
374 """!Tells each containing element to make its
375 produtil.prog.Runners into produtil.prog.ImmutableRunners so
376 that changes to them will not change the original."""
379 """!Returns a pythonic description of this object."""
383 reprs.append(repr(el))
384 return ' + '.join(reprs)
386 """!How many groups of identical repeated ranks are in this
395 """!How many ranks does this program request?"""
403 """!Iterates over tuples (rank,count) of groups of identical
406 for groups
in self.
_el:
407 for rank,count,threads
in groups.groups(threads=
True):
408 yield rank,count,threads
410 for groups
in self.
_el:
411 for rank,count
in groups.groups():
414 """!Iterates over groups of repeated ranks returning the number
415 of ranks each requests."""
416 for ranks
in self.
_el:
417 for rank
in ranks.ranks():
420 """!Adds more ranks to this program.
421 @param other an MPIRanksMPMD or MPIRanksSPMD to add"""
422 if isinstance(other,MPIRanksMPMD):
424 elif isinstance(other,MPIRank)
or isinstance(other,MPIRanksBase):
426 return NotImplemented
428 """!Prepends more ranks to this program.
429 @param other an MPIRanksMPMD or MPIRanksSPMD to prepend"""
430 if isinstance(other,MPIRanksMPMD):
432 elif isinstance(other,MPIRank)
or isinstance(other,MPIRanksBase):
434 return NotImplemented
436 """!Duplicates this MPMD program "factor" times.
437 @param factor how many times to duplicate this program."""
438 if isinstance(factor,int):
440 return NotImplemented
442 """!Duplicates this MPMD program "factor" times.
443 @param factor how many times to duplicate this program."""
444 if isinstance(factor,int):
447 """!Checks to see if this program contains serial (non-MPI) or
449 @returns a tuple (serial,parallel) where serial is True if
450 there are serial components, and parallel is True if there are
451 parallel components."""
455 (s,p)=el.check_serial()
456 serial = (serial
or s)
457 parallel = (parallel
or p)
458 return (serial,parallel)
460 """!Returns a logging.Logger for the first rank that has one."""
462 logger=el.get_logger()
463 if logger
is not None:
470 """!Represents a single MPI rank."""
472 """!MPIRank constructor.
473 @param arg What program to run. Can be a produtil.prog.Runner,
474 or some way of creating one, such as a program name or list
475 of program+arguments.
476 @param logger a logging.Logger for log messages or None to
480 if isinstance(arg,MPIRank):
483 self.
_args=list(arg._args)
486 self.
_args=[x
for x
in arg.args()]
489 'Tried to convert a Runner to an MPIRank directly, when '
490 'the Runner had more than an executable and arguments. '
491 'Use mpiserial instead.')
492 elif isinstance(arg,basestring):
494 elif isinstance(arg,list)
or isinstance(arg,tuple):
495 self.
_args=[x
for x
in arg]
498 'Input to MPIRank.__init__ must be a string, a list of '
499 'strings, or a Runner that contains only the executable '
500 'and its arguments.')
503 """!Returns the number of threads requested by this MPI rank,
504 or by each MPI rank in this group of MPI ranks."""
507 """!Sets the number of threads requested by each MPI rank
508 within this group of MPI ranks."""
512 """!Removes the request for threads."""
515 """!Return a POSIX sh representation of this MPI rank, if
519 """!Adds arguments to this MPI rank's program."""
521 if isinstance(args,basestring):
527 """!Returns a Pythonic representation of this object for
529 s=
'mpi(%s)'%(repr(self.
_args[0]))
530 if len(self.
_args)>1:
531 s=s+
'['+
','.join([repr(x)
for x
in self.
_args[1:]])+
']'
534 """!Returns a logging.Logger for this object, or None."""
537 """!Checks to see if this MPIRank is valid, or has errors.
538 @param more Arguments to the executable to validate.
539 @returns None if there are no errors, or raises a descriptive
541 for x
in self.
args():
542 if not isinstance(x,basestring):
544 'Executable and arguments must be strings.')
545 if more
is not None and len(more)>0:
547 if not isinstance(x,basestring):
549 'Executable and arguments must be strings.')
551 """!Iterates over the executable arguments."""
552 for arg
in self.
_args:
yield arg
554 """!Return a copy of self. This is a deep copy except for the
555 logger which whose reference is copied."""
558 """!Returns 1: the number of MPI ranks."""
561 """!Returns 1: the number of groups of identical ranks."""
564 """!Yields self once: all MPI ranks."""
567 """!Yields (self,1): all groups of identical ranks and the
574 """!Creates an MPIRanksSPMD or MPIRanksMPMD with this MPIRank
576 @param other The other ranks."""
577 if not isinstance(other,MPIRank):
578 return NotImplemented
584 """!Creates an MPIRanksSPMD with this MPIRank duplicated factor times.
585 @param factor the number of times to duplicate"""
586 if isinstance(factor,int):
588 return NotImplemented
590 """!Creates an MPIRanksSPMD with this MPIRank duplicated factor times.
591 @param factor the number of times to duplicate"""
592 if isinstance(factor,int):
594 return NotImplemented
596 """!Returns True if this MPIRank is equal to the other object."""
597 return self.
_args==other._args
599 """!Returns (False,True): this is a pure parallel program."""
605 """!Represents a single rank of an MPI program that is actually
606 running a serial program. This is supported directly by some
607 MPI implementations while others require kludges to work properly."""
609 """!MPISerial constructor."""
613 """!Creates a version of self with a produtil.prog.ImmutableRunner child."""
619 """!Duplicates self."""
622 """!Returns a pythonic string representation of self for debugging."""
623 return 'mpiserial(%s)'%(repr(self.
_runner),)
625 """!Iterates over command arguments of the child serial program."""
626 for arg
in self._runner.args():
629 """!Returns my logging.Logger that I use for log messages."""
632 return self._runner.get_logger()
636 """!Returns True if other is an MPISerial with the same Runner,
638 @param other the other object to compare against."""
639 return isinstance(other,MPISerial)
and other._runner==self._runner
641 """!Returns (True,False) because this is a serial program
642 (True,) and not a parallel program (,False)."""
645 """!Returns True if the child serial program is a plain
646 executable, False otherwise. See
647 produtil.prog.Runner.isplainexe() for details."""
648 return self._runner.isplainexe()
650 """!Returns a POSIX sh version of the child serial program."""
651 return self._runner.to_shell()
def delthreads(self)
Removes the request for threads.
def __add__(self, other)
Adds more ranks to this program.
def __mul__(self, factor)
Returns a new set of MPI ranks that consist of this group of ranks repeated "factor" times...
def to_shell(self)
Returns a POSIX sh version of the child serial program.
def make_runners_immutable(self)
Returns a new MPIRanksSPMD with an immutable version of self._mpirank.
def to_shell(self)
Returns a POSIX sh command that will execute the serial program, if possible, or raise a subclass of ...
def isplainexe(self)
Returns True if the child serial program is a plain executable, False otherwise.
def groups
Yields a tuple (X,N) where X is the mpi program and N is the number of ranks.
def __eq__(self, other)
Returns True if this part of the MPI program is the same as another part.
def ngroups(self)
Returns the number of groups of repeated MPI ranks in the MPI program.
def make_runners_immutable(self)
Returns a copy of this object where all child produtil.prog.Runner objects have been replaced with pr...
def delthreads(self)
Removes the request for threads.
def args(self)
Iterates over the executable arguments.
def __repr__(self)
Returns a Pythonic representation of this object for debugging.
Represents a group of MPI programs, each of which have some number of ranks assigned.
def validate
Checks to see if this MPIRank is valid, or has errors.
def get_logger(self)
Returns my logging.Logger that I use for log messages.
def __rmul__(self, factor)
Duplicates this MPMD program "factor" times.
def to_arglist
This is the underlying implementation of most of the mpi_impl modules, and hence make_runner as well...
def ranks(self)
Iterates over all MPIRank objects in this part of the MPI program.
def groups
Iterates over tuples (rank,count) of groups of identical ranks.
def __repr__(self)
Returns a string representation of this object intended for debugging.
def __repr__(self)
Returns "X*N" where X is the MPI program and N is the number of ranks.
def __init__
MPIRank constructor.
def setthreads(self, nthreads)
Sets the number of threads requested by each MPI rank within this group of MPI ranks.
def nranks(self)
Returns the number of ranks in this part of the MPI program.
def __getitem__(self, args)
Adds arguments to this MPI rank's program.
def __add__(self, other)
Add some new ranks to self.
def setthreads(self, nthreads)
Sets the number of threads requested by each MPI rank within this group of MPI ranks.
def ranks(self)
Iterates over MPI ranks within self.
def make_runners_immutable(self)
Creates a version of self with a produtil.prog.ImmutableRunner child.
Implements the produtil.run: provides the object tree for representing shell commands.
def check_serial(self)
Checks to see if this program contains serial (non-MPI) or MPI components.
def copy(self)
Return a copy of self.
def get_logger(self)
Returns a logging.Logger for the first rank that has one.
def __add__(self, other)
Returns a new set of MPI ranks that consist of this set of ranks with the "other" set appended...
Represents a single MPI rank.
def check_serial(self)
Returns (True,False) because this is a serial program (True,) and not a parallel program (...
def copy(self)
Duplicates self.
def validate(self)
Does nothing.
def isplainexe(self)
Determines if this set of MPI ranks can be represented by a single serial executable with a single se...
def __repr__(self)
Returns a pythonic description of this object.
def __mul__(self, factor)
Duplicates this MPMD program "factor" times.
def get_logger(self)
Returns a logger.Logger object for this MPIRanksBase or one from its child MPIRanksBase objects (if i...
def args(self)
Iterates over command arguments of the child serial program.
Base class of exceptions raised when a Runner is given arguments that make no sense.
def nranks(self)
Returns the number of ranks this program requests.
def __eq__(self, other)
Returns True if this MPIRank is equal to the other object.
def __rmul__(self, factor)
Creates an MPIRanksSPMD with this MPIRank duplicated factor times.
def __rmul__(self, other)
Returns a new set of MPI ranks that consist of this group of ranks repeated "factor" times...
def __radd__(self, other)
Returns a new set of MPI ranks that consist of the "other" set of ranks with this set appended...
def expand_iter
This is a wrapper around ranks() and groups() which will call self.groups() if expand=False.
def get_logger(self)
Returns my MPI program's logger.
Raised when a serial program was expected, but something else was given.
def __mul__(self, factor)
Creates an MPIRanksSPMD with this MPIRank duplicated factor times.
def __init__
MPISerial constructor.
Raised when an MPI program was expected but something else was given.
def __mul__(self, factor)
Multiply the number of requested ranks by some factor.
Represents one MPI program duplicated across many ranks.
def getthreads(self)
Returns the number of threads requested by this MPI rank, or by each MPI rank in this group of MPI ra...
def __radd__(self, other)
Prepends more ranks to this program.
def to_shell(self)
Return a POSIX sh representation of this MPI rank, if possible.
def get_logger(self)
Returns a logging.Logger for this object, or None.
def ranks(self)
Yields self once: all MPI ranks.
Represents a single rank of an MPI program that is actually running a serial program.
def check_serial(self)
Returns (False,True): this is a pure parallel program.
def __init__(self, mpirank, count)
MPIRanksSPMD constructor.
def __rmul__(self, factor)
Multiply the number of requested ranks by some factor.
def shbackslash(s)
Given a Python str, returns a backslashed POSIX sh string, or raises NotValidPosixShString if that ca...
This is the abstract superclass of all classes that represent one or more MPI ranks, including MPI ranks that are actually serial programs.
def getthreads(self)
Returns the number of threads requested by this MPI rank, or by each MPI rank in this group of MPI ra...
def check_serial(self)
Checks to see if this program contains serial (non-MPI) or MPI components.
def ranks(self)
Iterates over groups of repeated ranks returning the number of ranks each requests.
def __add__(self, other)
Creates an MPIRanksSPMD or MPIRanksMPMD with this MPIRank and the other ranks.
def copy(self)
Returns a deep copy of self.
def make_runners_immutable(self)
Tells each containing element to make its produtil.prog.Runners into produtil.prog.ImmutableRunners so that changes to them will not change the original.
Base class of syntax errors in MPI program specifications.
def ngroups(self)
Returns 1 or 0: 1 if there are ranks and 0 if there are none.
def ngroups(self)
Returns 1: the number of groups of identical ranks.
Represents a single stage of a pipeline to execute.
def nranks(self)
How many ranks does this program request?
def __eq__(self, other)
Returns True if other is an MPISerial with the same Runner, False otherwise.
def ngroups(self)
How many groups of identical repeated ranks are in this MPMD program?
An copy-on-write version of Runner.
def __repr__(self)
Returns a pythonic string representation of self for debugging.
def check_serial(self)
Returns a tuple (s,p) where s=True if there are serial ranks in this part of the MPI program...
def groups
Yields (self,1): all groups of identical ranks and the number per group.
def groups
Iterates over all groups of repeating MPI ranks in the MPI program returning tuples (r...
def __init__(self, args)
MPIRanksMPMD constructor.
def nranks(self)
Returns 1: the number of MPI ranks.