Jobs¶
The Job
class is undoubtedly the most important object in PLAMS.
Job is the basic piece of computational work and running jobs is the main goal of PLAMS scripts.
Various jobs may differ in details quite a lot, but they all follow the same set of common rules defined in the abstract class Job
.
Note
Being an abstract class means that the Job
class has some abstract methods – methods that are declared but not implemented (they do nothing).
Those methods are supposed to be defined in subclasses of Job
.
When a subclass of an abstract class defines all required abstract methods, it is called a concrete class.
You should never create an instance of an abstract class, because when you try to use it, empty abstract methods are called and your script crashes.
Every job has its own unique name and a separate folder (called the job folder, with the same name as the job) located in the main working folder. All files regarding that particular job (input, output, runscript, other files produced by the job execution) end up in the job folder.
In general, a job can be of one of two types: a single job or a multijob.
These types are defined as subclasses of the Job
class: SingleJob
and MultiJob
.
Single job is a job representing a single calculation, usually done by executing an external binary (ADF, Dirac etc.).
Single job creates a runscript (which is usually just a shell script) that is then either executed locally or submitted to some external queueing system.
As a result of running a single job multiple files are created, including dumps of the standard output and standard error streams, together with any other files produced by that external binary.
SingleJob
is still an abstract class that is further subclassed by program-specific concrete classes like for example AMSJob
.
Multijob, on the other hand, does not run any calculation by itself.
It is a container for other jobs, used to aggregate smaller jobs into bigger ones.
There is no runscript produced by a multijob.
Instead, it contains a list of subjobs called children that are run together when the parent job is executed.
Children jobs can in turn be either single or multijobs.
Job folder of each child job is a subfolder of its parent’s folder, so the folder hierarchy on the filesystem reflects the child-parent hierarchy of jobs.
MultiJob
is a concrete class so you can create its instances and run them.
Preparing a job¶
The first step to run a job using PLAMS is to create a Job
instance.
You need to pick a concrete class that defines a type of job you want to run (AMSJob
will be used as an example in our case) and create its instance:
myjob = AMSJob(name='myfirstjob')
Various keyword arguments (arguments of the form arg=value
, like name
in the example above) can be passed to a job constructor, depending on the type of your job.
The following keyword arguments are common for all types of jobs:
name
– a string containing the name of the job. If not supplied, default nameplamsjob
is used. Job name cannot contain path separator (\
in Linux,/
in Windows).settings
– aSettings
instance to be used by this job. It gets copied (usingcopy()
) so you can pass the same instance to several different jobs and changes made afterwards won’t interfere. Any instance ofJob
can be also passed as a value of this argument. In that caseSettings
associated with the passed job are copied.depend
– a list of jobs that need to be finished before this job can start. This is useful when you want to execute your jobs in parallel. Usually there is no need to use this argument, since dependencies between jobs are resolved automatically (see Synchronization of parallel job executions). However, sometimes one needs to explicitly specify such a dependency anddepend
option is meant for that.
Those values do not need to be passed to the constructor, they can be set or changed later (but they should be fixed before the job starts to run):
myjob = AMSJob()
myjob.name = 'myfirstjob'
myjob.settings.runscript.pre = 'echo HelloWorld'
Single jobs can be supplied with another keyword argument, molecule
.
It is supposed to be a Molecule
object.
Multijobs, in turn, accept a keyword argument children
that stores the collection of children jobs (usually a list or a dictionary).
The most meaningful part of each job object is its Settings
instance.
It is used to store information about contents of the input file and the runscript as well as other tweaks of job’s behavior.
Thanks to the tree-like structure of Settings
this information is organized in a convenient way: the top level (myjob.settings
) stores general settings, myjob.settings.input
is a branch for specifying input settings, myjob.settings.runscript
holds information for runscript creation and so on.
Some types of jobs will make use of their own myjob.settings
branches and not every kind of job will require input
or runscript
branches (like multijob for example).
The nice thing is that all the unnecessary data present in job settings is simply ignored, so accidentally plugging settings with too much data will not cause any problem (except some cases where the whole content of some branch is used, like for example the input
branch in AMSJob
).
Contents of job settings¶
The following keys and branches of job settings are meaningful for all kinds of jobs:
myjob.settings.input
is a branch storing settings regarding input file of a job. The way in which the data present ininput
branch is used depends on the type of job and is specified in respective subclasses ofJob
.myjob.settings.runscript
holds runscript information, either program-specific or general:myjob.settings.runscript.shebang
– the first line of the runscript, starting with#!
, describing an interpreter to usemyjob.settings.runscript.pre
– an arbitrary string that will be placed in the runscript file just below the shebang line, before the actual contentsmyjob.settings.runscript.post
– an arbitrary string to put at the end of the runscriptmyjob.settings.runscript.stdout_redirect
– boolean flag defining if standard output redirection should be handled inside the runscript. If set toFalse
, the redirection will be done by Python from outside the runscript. If set toTrue
, standard output will be redirected inside the runscript using>
myjob.settings.run
branch stores run flags for the job. Run flags is a flat collection of key-value pairs that are used byGridRunner
to construct a command used to submit the runscript to a queueing system (like, for example, number of nodes/cores or size of the memory used withqsub
orsbatch
)myjob.settings.keep
andmyjob.settings.save
are keys adjusting Cleaning job folder.myjob.settings.pickle
is a boolean value defining if the job object should be pickled after finishing (see Pickling)myjob.settings.link_files
is a boolean value defining if files from the job folder can be linked rather than copied when copying is requested
Default settings¶
Every job instance has an attribute called default_settings
that stores a list of Settings
instances that serve as default templates for that job.
Initially that list contains only one element, the global defaults for all jobs stored in config.job
.
You can add other templates by simply adding new elements to that list:
myjob.default_settings.append(sometemplate)
myjob.default_settings += [temp1, temp2]
During job execution, just after prerun()
method is finished, job’s own settings
are soft-updated with all elements of default_settings
list, one by one, starting with the last one.
That way, if you want to adjust some setting for all jobs present in your script, you don’t need to do it for each job separately, one change in config.job
is enough.
Similarly, if you have a group of jobs that need the same settings
adjustments, you can create an empty Settings
instance, put those adjustments in it and add it to each job’s default_settings
.
Keep in mind that soft_update()
is used here, so a key in a template in default_settings
will end up in job’s final settings
only if such a key is not yet present there.
Thanks to that the order of templates in default_settings
corresponds to their importance.
The data from an “earlier” template will never override the data from a “later” one, it can only enrich it.
In the example below:
s = Settings()
s.input.ams.Task = 'SinglePoint'
s.input.ams.UseSymmetry = 'True'
t = Settings()
t.input.ams.Task = 'GeometryOptimization'
myjob = AMSJob(...)
myjob.default_settings += [s,t]
myjob.run()
Task SinglePoint
from s
becomes replaced by Task GeometryOptimization
from t
, but UseSymmetry True
from s
stays and ends up in the final settings of myjob
.
Running a job¶
After creating a Job
instance and adjusting its settings you can finally run it.
It is done by invoking the run()
method, which returns a Results
object:
myresults = myjob.run()
(the Results
object can be also accessed as myjob.results
).
Various keyword arguments can be passed to run()
.
With jobrunner
and jobmanager
you can specify which JobRunner
and JobManager
to use for your job.
If those arguments are omitted, the default instances stored in config.default_jobrunner
and config.default_jobmanager
are taken.
All other keyword arguments are collected and stored in myjob.settings.run
branch of job settings, as one flat level.
They can be used later by various objects involved in running your job, for example GridRunner
uses them to build the command executed to submit the runscript to the queueing system.
The following steps are taken after the run()
method is called:
myjob.settings.run
is soft-updated withrun()
keyword arguments (so arun()
argument will never override anything already present inmyjob.settings.run
).- If a parallel
JobRunner
was used, a new thread is spawned and all further steps of this list happen in that thread. - Explicit dependencies from
myjob.depend
are resolved. This means waiting for all jobs listed independ
to finish. - Job name gets registered in the job manager and the job folder is created. If a job with the same name has been registered before, a new unique name is created.
- Job’s
prerun()
method is called. myjob.settings
are updated with the contents ofmyjob.default_settings
(see Default settings).- The hash of a job is calculated and checked (see Rerun prevention).
If the same job was found as previously run, its results are copied (or linked) to the current job’s folder and
run()
method finishes. - Now the real job execution happens.
If the job is a single job, an input file and a runscript are created and passed to job runner’s method
call()
. If the job is a multijob,run()
method is called for all children jobs. - After the execution is finished, result files produced by the job are collected and
check()
is used to test if the execution was successful. - The job folder is cleaned using
myjob.settings.keep
. See Cleaning job folder for details. - Job’s
postrun()
method is called. - If
myjob.settings.pickle
is set toTrue
, the whole job instance gets pickled and saved to the[jobname].dill
file in the job folder. See Pickling for details
Name conflicts¶
Jobs are identified by their names, so names need to be unique.
This is necessary also because job name corresponds to the name of its folder.
Usually it is recommended to manually set unique names of your jobs (via name
argument of job constructor) for easier navigation through results.
But for some applications, especially the ones requiring running large numbers of similar jobs, this would be very cumbersome.
PLAMS automatically resolves conflicts between job names.
During step 4. of the above list, if a job with the same name was already registered, the current job is renamed.
The new name is created by appending some number to the old name.
For example, the second job with the name plamsjob
will be renamed to plamsjob.002
, third to plamsjob.003
and so on.
Number of digits used in this counter can be adjusted via config.jobmanager.counter_len
and the default value is 3.
Overflowing the counter will not cause any problems, the job coming after plamsjob.999
will be called plamsjob.1000
.
Prerun and postrun methods¶
prerun()
and postrun()
methods are intended for further customization of your jobs.
They can contain arbitrary pieces of code that are executed, respectively, before or after the actual execution of your job.
prerun()
method is run after the job folder is created but before hash checking.
Here are some ideas what can be put there:
- adjusting job settings
- copying to the job folder some files required for the execution
- extracting results of some other job, processing them and plugging to the current job (for example, extracting optimized geometry from a previous geometry optimization)
- generating children jobs in multijobs
See also Synchronization of parallel job executions for an explanation on how to use prerun()
to automatically handle dependencies in parallel workflows.
The other method, postrun()
, is called after job execution is finished, the results are collected and the job folder is cleaned.
It is supposed to contain any kind of essential results postprocessing that needs to be done before results of this job can be pushed further in the workflow.
For that purpose code contained in postrun()
has some special privileges.
At the time the method is executed the job is not yet considered done, so all threads requesting its results are waiting.
However, the guardian restricting the access to results of unfinished jobs can recognize code coming from postrun()
and allow it to access and modify results.
Thanks to that calling Results
methods can be safely done in postrun()
and you can be sure that everything that happens in postrun()
is done before other jobs have access to the final job’s results.
prerun()
and postrun()
methods can be added to your jobs in multiple ways:
you can create a tiny subclass which redefines the method:
class MyJobWithPrerun(MyJob): def prerun(self): #do stuff
It can be done right inside you script. After the above definition you can create instances of the new class and treat them in exactly the same way you would treat
MyJob
instances. The only difference is that they will be equipped withprerun()
method you just defined.you can bind the method to an existing class using
add_to_class()
decorator:@add_to_class(MyJob) def prerun(self): #do stuff
That change affects all instances of
MyJob
, even those created before the above code was executed (obviously it won’t affect instances previously run and finished).you can bind the method directly to an instance using
add_to_instance()
decorator:j = MyJob(...) @add_to_instance(j) def prerun(self): #do stuff
In that case, only one specified instance
j
is affected with the newprerun()
.
All the above works for postrun()
as well.
Preview mode¶
Preview mode is a special way of running jobs without the actual runscript execution. In this mode the procedure of running a job is interrupted just after input and runscript files are written to the job folder. Preview mode can be used to check if your jobs generate proper input and runscript files, without having to run the full calculation.
You can enable the preview mode by putting the following line at the beginning of your script:
config.preview = True
Job API¶
-
class
Job
(name='plamsjob', settings=None, depend=None)[source]¶ General abstract class for all kind of computational tasks.
Methods common for all kinds of jobs are gathered here. Instances of
Job
should never be created. It should not be subclassed either. If you wish to define a new type of job please subclass eitherSingleJob
orMultiJob
.Methods that are meant to be explicitly called by the user are
run()
and occasionallypickle()
. In most cases Pickling is done automatically, but if for some reason you wish to do it manually, you can usepickle()
method.Methods that can be safely overridden in subclasses are:
check()
hash()
(see Rerun prevention)prerun()
andpostrun()
(see Prerun and postrun methods)
All other methods should remain unchanged.
Class attribute
_result_type
defines the type of results associated with this job. It should point to a class and it must be aResults
subclass.Every job instance has the following attributes:
Attributes adjusted automatically that should not be changed by the user:
status
– current status of the job. Possible values: created, started, registered, running, finished, crashed, failed, successful, copied, preview.results
– a reference to a results instance. An empty instance of the type stored in_result_type
is created when the job object is created.path
– an absolute path to the job folder.jobmanager
– a job manager associated with this job.parent
– a pointer to the parent job if this job is a child job of someMultiJob
.None
otherwise.
Attributes that can be modified, but only before
run()
is called:name
– the name of the job.settings
– settings of the job.default_settings
– see Default settings.depend
– a list of explicit dependencies._dont_pickle
– additional list of this instance’s attributes that will be removed before pickling. See Pickling for details.
-
__init__
(name='plamsjob', settings=None, depend=None)[source]¶ Initialize self. See help(type(self)) for accurate signature.
-
run
(jobrunner=None, jobmanager=None, **kwargs)[source]¶ Run the job using jobmanager and jobrunner (or defaults, if
None
). Other keyword arguments (**kwargs) are stored inrun
branch of job’s settings. Returned value is theResults
instance associated with this job.Warning
This method should not be overridden.
Technical
This method does not do too much by itself. After some initial preparation it passes control to the job runner, which decides if a new thread should be started for this job. The role of the job runner is to execute three methods that make the full job life cycle:
_prepare()
,_execute()
and_finalize()
. During_execute()
the job runner is called once again to execute the runscript (only in case ofSingleJob
).
-
pickle
(filename=None)[source]¶ Pickle this instance and save to a file indicated by filename. If
None
, save to[jobname].dill
in the job folder.
-
ok
(strict=True)[source]¶ Check if the execution of this instance was successful. If needed, wait for the job to finish and then check if the status is successful (or copied).
If this method is called before job’s
run()
method, a warning is logged and the returned value isFalse
. The most likely cause of such a behavior is simply forgetting aboutrun()
call. However, in complicated workflows executed in parallel, it can sometimes naturally happen that one thread is ahead of others and callsok()
before some other thread has a chance to callrun()
. If you’re experiencing that kind of problems, please consider usingstrict=False
to skip therun()
check. But keep in mind that skipping that check will deadlock the current thread ifrun()
never gets called.
-
check
()[source]¶ Check if the execution of this instance was successful. Abstract method meant for internal use.
-
prerun
()[source]¶ Actions to take before the actual job execution.
This method is initially empty, it can be defined in subclasses or directly added to either the whole class or a single instance using Binding decorators.
-
postrun
()[source]¶ Actions to take just after the actual job execution.
This method is initially empty, it can be defined in subclasses or directly added to either the whole class or a single instance using Binding decorators.
-
_prepare
(jobmanager)[source]¶ Prepare the job for execution. This method collects steps 1-7 from Running a job. Should not be overridden. Returned value indicates if job execution should continue (Rerun prevention did not find this job as previously run).
-
_get_ready
()[source]¶ Get ready for
_execute()
. This is the last step before_execute()
is called. Abstract method.
-
_finalize
()[source]¶ Gather the results of the job execution and organize them. This method collects steps 9-12 from Running a job. Should not be overridden.
Single jobs¶
-
class
SingleJob
(molecule=None, name='plamsjob', settings=None, depend=None)[source]¶ Abstract class representing a job consisting of a single execution of some external binary (or arbitrary shell script in general).
In addition to constructor arguments and attributes defined by
Job
, the constructor of this class accepts the keyword argumentmolecule
that should be aMolecule
instance. The constructor creates a copy of the suppliedMolecule
and stores it as themolecule
attribute.Class attribute
_filenames
defines default names for input, output, runscript and error files. If you wish to override this attribute it should be a dictionary with string keys'inp'
,'out'
,'run'
,'err'
. The value for each key should be a string describing corresponding file’s name. Shortcut$JN
can be used for job’s name. The default value is defined in the following way:_filenames = {'inp':'$JN.in', 'run':'$JN.run', 'out':'$JN.out', 'err': '$JN.err'}
This class defines no new methods that could be directly called in your script. Methods that can and should be overridden are
get_input()
andget_runscript()
.-
__init__
(molecule=None, **kwargs)[source]¶ Initialize self. See help(type(self)) for accurate signature.
-
get_input
()[source]¶ Generate the input file. Abstract method.
This method should return a single string with the full content of the input file. It should process information stored in the
input
branch of job settings and in themolecule
attribute.
-
get_runscript
()[source]¶ Generate the runscript. Abstract method.
This method should return a single string with the runscript contents. It can process information stored in
runscript
branch of job settings. In general the full runscript has the following form:[first line defined by job.settings.runscript.shebang] [contents of job.settings.runscript.pre, when present] [value returned by get_runscript()] [contents of job.settings.runscript.post, when present]
When overridden, this method should pay attention to
.runscript.stdout_redirect
key in job’ssettings
.
-
hash
()[source]¶ Calculate unique hash of this instance.
The behavior of this method is adjusted by the value of
hashing
key inJobManager
settings. If noJobManager
is yet associated with this job, default setting fromconfig.jobmanager.hashing
is used.Methods
hash_input()
andhash_runscript()
are used to obtain hashes of, respectively, input and runscript.Currently supported values for
hashing
are:False
orNone
– returnsNone
and disables Rerun prevention.input
– returns the hash of the input file.runscript
– returns the hash of the runscript.input+runscript
– returns SHA256 hash of the concatenation of hashes of input and runscript.
-
check
()[source]¶ Check if the calculation was successful.
This method can be overridden in concrete subclasses of
SingleJob
. It should return a boolean value. The definition here serves as a default, to prevent crashing if a subclass does not define its owncheck()
. It always returnsTrue
.
-
full_runscript
()[source]¶ Generate the full runscript, including shebang line and contents of
pre
andpost
, if any. In practice this method is just a simple wrapper aroundget_runscript()
.
-
_get_ready
()[source]¶ Create input and runscript files in the job folder. Methods
get_input()
andfull_runscript()
are used for that purpose. Filenames correspond to entries in the _filenames attribute
-
_execute
(jobrunner)[source]¶ Execute previously created runscript using jobrunner.
The method
call()
of jobrunner is used. Working directory isself.path
.self.settings.run
is passed asrunflags
argument.If preview mode is on, this method does nothing.
-
classmethod
load_external
(path, settings=None, molecule=None, finalize=False)[source]¶ Load an external job from path.
In this context an “external job” is an execution of some external binary that was not managed by PLAMS, and hence does not have a
.dill
file. It can also be used in situations where the execution was started with PLAMS, but the Python process was terminated before the execution finished, resulting in steps 9-12 of Running a job not happening.All the files produced by your computation should be placed in one folder and path should be the path to this folder or a file in this folder. The name of the folder is used as a job name. Input, output, error and runscript files, if present, should have names defined in
_filenames
class attribute (usually[jobname].in
,[jobname].out
,[jobname].err
and[jobname].run
). It is not required to supply all these files, but in most cases one would like to use at least the output file, in order to use methods likegrep_output()
orget_output_chunk()
.This method is a class method, so it is called via class object and it returns an instance of that class:
>>> j = SingleJob.load_external(path='some/path/jobname') >>> type(j) scm.plams.core.basejob.SingleJob >>> a = AMSJob.load_external(path='some/path/jobname') >>> type(a) scm.plams.interfaces.adfsuite.ams.AMSJob
You can supply
Settings
andMolecule
instances as settings and molecule parameters, they will end up attached to the returned job instance. If you don’t do this, PLAMS will try to recreate them automatically using methodsrecreate_settings()
andrecreate_molecule()
of the correspondingResults
subclass. If noSettings
instance is obtained in either way, the defaults fromconfig.job
are copied.You can set the finalize parameter to
True
if you wish to run the whole_finalize()
on the newly created job. In that case PLAMS will perform the usualcheck()
to determine the job status (successful or failed), followed by cleaning of the job folder (Cleaning job folder),postrun()
and pickling (Pickling). If finalize isFalse
, the status of the returned job is copied.
-
Subclassing SingleJob¶
SingleJob
class was designed in a way that makes subclassing it quick and easy.
Thanks to that it takes very little effort to create a PLAMS interface for new external binary.
Your new class has to, of course, be a subclass of SingleJob
and define methods get_input()
and get_runscript()
:
class MyJob(SingleJob):
def get_input(self):
...
return 'string with input file'
def get_runscript(self):
...
return 'string with runscript'
Note
get_runscript()
method should properly handle output redirection based on the value of myjob.settings.runscript.stdout_redirect
.
When False
, no redirection should occur inside the runscript.
If True
, the runscript should be constructed in such a way that the standard output is redirected (using >
) to the output file, which name can be accessed as self._filename('out')
.
This is sufficient for your new job to work properly with other PLAMS components. However, there are other useful attributes and methods that can be overridden:
check()
– the default version of this method defined inSingleJob
always returnsTrue
and hence disables any correctness checking. If you wish to enable checking for your new class, you need to definecheck()
method in it, just likeget_input()
andget_runscript()
in the example above. It should take no other arguments thanself
and return a boolean value indicating if the job execution was successful. This method is privileged to have an early access toResults
methods in exactly the same way aspostrun()
.if you wish to create a special
Results
subclass for results of your new job, make sure to let the job know about it:class MyResults(Results): def get_some_results(self, ...): ... class MyJob(SingleJob): _result_type = MyResults def get_input(self): ... return 'string with input file' def get_runscript(self): ... return 'string with runscript'
hash_input()
andhash_runscript()
– see Rerun prevention for details.if your new job requires some special preparations regarding input or runscript files these preparations can be done for example in
prerun()
. However, if you wish to leaveprerun()
clean for further subclassing or adjusting in instance-based fashion, you can use another method called_get_ready()
. This method is responsible for input and runscript creation, so if you decide to override it you must call its parent version in your new version:def _get_ready(self): # do some stuff SingleJob._get_ready() # do some other stuff
Warning
Whenever you are subclassing any kind of job, either single of multi, and you wish to override its constructor (__init__
method) it is absolutely essential to call the parent constructor and pass all unused keyword arguments to it:
class MyJob(SingleJob):
def __init__(self, myarg1, myarg2=default2, **kwargs):
SingleJob.__init__(self, **kwargs)
# do stuff with myarg1 and myarg2
Multijobs¶
-
class
MultiJob
(children=None, name='plamsjob', settings=None, depend=None)[source]¶ Concrete class representing a job that is a container for other jobs.
In addition to constructor arguments and attributes defined by
Job
, the constructor of this class accepts two keyword arguments:children
– iterable container with children jobs (usually a list or a dictionary).childrunner
– by default all the children jobs are run using the sameJobRunner
as the parent job. If you wish to use a differentJobRunner
for children, you can pass it usingchildrunner
.
Values passed as
children
andchildrunner
are stored as instance attributes and can be adjusted later, but before therun()
method is called.This class defines no new methods that could be directly called in your script.
When executed, a multijob runs all its children using the same
JobManager
andJobRunner
(unlesschildrunner
is set). If you need to specify different run flags for children you can do it by manually setting them in child job settings:childjob = myjob.children[0] childjob.settings.run.arg = 'value'
Since the
run
branch of settings gets soft-updated with run flags, the value set this way is not overwritten by parent job.The job folder of a multijob gets cleaned independently of its children. See Cleaning job folder for details.
Private attributes
_active_children
and_lock
are essential for proper parallel execution. Please do not modify them.-
__init__
(children=None, childrunner=None, **kwargs)[source]¶ Initialize self. See help(type(self)) for accurate signature.
-
new_children
()[source]¶ Generate new children jobs.
This method is useful when some of children jobs are not known beforehand and need to be generated based on other children jobs, like for example in any kind of self-consistent procedure.
The goal of this method is to produce a new portion of children jobs. Newly created jobs should be returned in a container compatible with
self.children
(e.g. list for list, dict for dict). No adjustment of newly created jobs’parent
attribute is needed. This method cannot modify_active_children
attribute.The method defined here is a default template, returning
None
, which means no new children jobs are generated and the entire execution of the parent job consists only of running jobs initially found inself.children
. To modify this behavior you can override this method in aMultiJob
subclass or you can use one of Binding decorators, just like with Prerun and postrun methods.
-
check
()[source]¶ Check if the execution of this instance was successful, by calling
Job.ok()
of all the children jobs.
-
other_jobs
()[source]¶ Iterate through other jobs that belong to this
MultiJob
, but are not inchildren
.Sometimes
prerun()
orpostrun()
methods create and run some small jobs that don’t end up inchildren
collection, but are still considered a part of aMultiJob
instance (theirparent
atribute points to theMultiJob
and their working folder is inside MultiJob’s working folder). This method provides an iterator that goes through all such jobs.Each attribute of this
MultiJob
that is of typeJob
and has it’s parent pointing to thisMultiJob
is returned, in a random order.
-
_get_ready
()[source]¶ Get ready for
_execute()
. Count children jobs and set theirparent
attribute.
-
_notify
()[source]¶ Notify this job that one of its children has finished.
Decrement
_active_children
by one. Use_lock
to ensure thread safety.
-
_execute
(jobrunner)[source]¶ Run all children from
children
. Then usenew_children()
and run all jobs produced by it. Repeat this procedure untilnew_children()
returns an empty list. Wait for all started jobs to finish.
Using MultiJob¶
MultiJob
can be used in two ways: either by creating instances of it or by subclassing it.
The simplest application is just to use an instance of MultiJob
as a container grouping similar jobs that you wish to run at the same time using the same job runner:
mj = MultiJob(name='somejobs', children=[job1, job2, job3])
mj.children.append(job4)
mj.run(...)
Such a “container job” can further be customized with Prerun and postrun methods.
For example, postrun()
can be used to collect the relevant data from all children jobs and store it in an easily accessible way in mutlijob’s Results
.
A more flexible way of using MultiJob
is by creating your own subclasses of it.
That way you can enclose several jobs that are conceptually similar in one convenient “unit”, for example:
MultiJob
running a chain of single jobs: take a molecule, preoptimize its geometry using some approximate method, follow with a high-level geometry optimization and then use the optimized geoemtry for some properties calculationMultiJob
comparing different values of some parameter: run multiple single jobs with the same molecule and settings, but differing in one parameter (for example: XC functional, numerical accuracy, basis set etc.) for the sake of investigating the influence of that parameter on the final resultsMultiJob
running different geometries of the same system to investigate some property of the potential energy surface. A classic example here would be numerical gradient or numerical hessian calculations.
You can find examples of the above applications (and many more) in TODO: cookbook link.